elcrm 1.1.3 → 1.1.5

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
@@ -533,19 +533,112 @@ var init_form_aliases = __esm(() => {
533
533
  ];
534
534
  });
535
535
 
536
- // src/lib/migrate/form-tokens.ts
537
- import { readFileSync as readFileSync9 } from "fs";
538
- function removeDecl(css, name) {
539
- const re = new RegExp(`\\n?[ \\t]*${name.replace(/-/g, "\\-")}\\s*:[^;\\n}]+;?[ \\t]*`, "g");
536
+ // src/lib/migrate/css-decl.ts
537
+ function removeCssDecl(css, name) {
538
+ const re = new RegExp(`\\n?[ \\t]*${name.replace(/-/g, "\\-")}(?![\\w-])\\s*:`, "g");
540
539
  let n = 0;
541
- const next = css.replace(re, () => {
540
+ const ranges = [];
541
+ let m;
542
+ while ((m = re.exec(css)) !== null) {
543
+ const start = m.index;
544
+ let i = m.index + m[0].length;
545
+ let depth = 0;
546
+ while (i < css.length) {
547
+ const ch = css[i];
548
+ if (ch === "(")
549
+ depth++;
550
+ else if (ch === ")")
551
+ depth = Math.max(0, depth - 1);
552
+ else if ((ch === ";" || ch === "}") && depth === 0) {
553
+ if (ch === ";")
554
+ i++;
555
+ break;
556
+ }
557
+ i++;
558
+ }
559
+ let s = start;
560
+ if (s > 0 && css[s - 1] === `
561
+ `)
562
+ s--;
563
+ ranges.push({ start: s, end: i });
542
564
  n++;
543
- return "";
544
- });
565
+ }
566
+ if (!ranges.length)
567
+ return { next: css, n: 0 };
568
+ let next = css;
569
+ for (let r = ranges.length - 1;r >= 0; r--) {
570
+ const { start, end } = ranges[r];
571
+ next = next.slice(0, start) + next.slice(end);
572
+ }
545
573
  return { next: next.replace(/\n{3,}/g, `
546
574
 
547
575
  `), n };
548
576
  }
577
+
578
+ // src/lib/migrate/css-sanitize.ts
579
+ import { readFileSync as readFileSync9 } from "fs";
580
+ function stripOrphanColorMix(source) {
581
+ let next = source;
582
+ let hits = 0;
583
+ next = next.replace(ORPHAN_MIX_BLOCK_RE, () => {
584
+ hits++;
585
+ return `
586
+ `;
587
+ });
588
+ const lines = next.split(/\r?\n/);
589
+ const kept = [];
590
+ for (const line of lines) {
591
+ if (/^[ \t]*in\s+(?:srgb|hsl|hwb|lab|lch)\s*,?\s*$/i.test(line) || /^[ \t]*transparent\s*\)\s*;?\s*$/i.test(line) || /^[ \t]*var\([^)]+\)\s*\d+%\s*,?\s*$/i.test(line)) {
592
+ const prev = kept[kept.length - 1] ?? "";
593
+ if (!/color-mix\s*\(\s*$/i.test(prev) && !/color-mix\s*\(/i.test(prev)) {
594
+ hits++;
595
+ continue;
596
+ }
597
+ }
598
+ kept.push(line);
599
+ }
600
+ next = kept.join(`
601
+ `).replace(/\n{3,}/g, `
602
+
603
+ `);
604
+ return { next, hits };
605
+ }
606
+ function lineHasOrphanColorMix(line) {
607
+ const t = line.trim();
608
+ if (!t || t.startsWith("/*") || t.startsWith("*"))
609
+ return false;
610
+ if (/^in\s+(?:srgb|hsl|hwb|lab|lch)\b/i.test(t) && !/color-mix\s*\(/i.test(t)) {
611
+ return true;
612
+ }
613
+ if (/^transparent\s*\)\s*;?\s*$/i.test(t))
614
+ return true;
615
+ return false;
616
+ }
617
+ async function migrateCssSanitize(cwd, dry) {
618
+ const changes = [];
619
+ const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r)).filter((f) => f.endsWith(".css"));
620
+ for (const file of files) {
621
+ const raw = readFileSync9(file, "utf8");
622
+ const { next, hits } = stripOrphanColorMix(raw);
623
+ if (hits === 0 || next === raw)
624
+ continue;
625
+ if (writeIfChanged(file, next, dry) || dry) {
626
+ changes.push({
627
+ file: rel(cwd, file),
628
+ detail: `${hits}\xD7 \u0443\u0434\u0430\u043B\u0435\u043D\u044B \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0435 color-mix/\u0444\u0440\u0430\u0433\u043C\u0435\u043D\u0442\u044B`
629
+ });
630
+ }
631
+ }
632
+ return changes;
633
+ }
634
+ var ORPHAN_MIX_BLOCK_RE;
635
+ var init_css_sanitize = __esm(() => {
636
+ init_walk();
637
+ ORPHAN_MIX_BLOCK_RE = /(?:^|\n)[ \t]*(?:in\s+srgb|in\s+hsl|in\s+hwb|in\s+lab|in\s+lch)[\s\S]*?\n[ \t]*\);?[ \t]*(?=\n|$)/gi;
638
+ });
639
+
640
+ // src/lib/migrate/form-tokens.ts
641
+ import { readFileSync as readFileSync10 } from "fs";
549
642
  function migrateDatePopupShadowDecl(css) {
550
643
  let n = 0;
551
644
  let next = css;
@@ -600,10 +693,13 @@ function rewriteFormTokens(source) {
600
693
  const stillUsed = new RegExp(`var\\(\\s*${name.replace(/-/g, "\\-")}\\s*`, "i").test(next);
601
694
  if (stillUsed)
602
695
  continue;
603
- const rem = removeDecl(next, name);
696
+ const rem = removeCssDecl(next, name);
604
697
  next = rem.next;
605
698
  hits += rem.n;
606
699
  }
700
+ const clean = stripOrphanColorMix(next);
701
+ next = clean.next;
702
+ hits += clean.hits;
607
703
  return { next, hits };
608
704
  }
609
705
  async function migrateFormTokens(cwd, dry) {
@@ -613,7 +709,7 @@ async function migrateFormTokens(cwd, dry) {
613
709
  const changes = [];
614
710
  const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r)).filter((f) => f.endsWith(".css"));
615
711
  for (const file of files) {
616
- const raw = readFileSync9(file, "utf8");
712
+ const raw = readFileSync10(file, "utf8");
617
713
  const { next, hits } = rewriteFormTokens(raw);
618
714
  if (hits === 0 || next === raw)
619
715
  continue;
@@ -642,6 +738,7 @@ var VAR_REPLACEMENTS, DECL_REMOVE, FORM_TOKEN_DEPRECATED, DEPRECATED_VAR_RE, DEP
642
738
  var init_form_tokens = __esm(() => {
643
739
  init_pkg();
644
740
  init_walk();
741
+ init_css_sanitize();
645
742
  VAR_REPLACEMENTS = [
646
743
  [
647
744
  /var\(\s*--field-note-padding-top\s*(?:,[^)]+)?\)/gi,
@@ -689,18 +786,7 @@ var init_form_tokens = __esm(() => {
689
786
  });
690
787
 
691
788
  // src/lib/migrate/components.ts
692
- import { readFileSync as readFileSync10 } from "fs";
693
- function removeDecl2(css, name) {
694
- const re = new RegExp(`\\n?[ \\t]*${name.replace(/-/g, "\\-")}\\s*:[^;\\n}]+;?[ \\t]*`, "g");
695
- let n = 0;
696
- const next = css.replace(re, () => {
697
- n++;
698
- return "";
699
- });
700
- return { next: next.replace(/\n{3,}/g, `
701
-
702
- `), n };
703
- }
789
+ import { readFileSync as readFileSync11 } from "fs";
704
790
  function rewriteComponentsCss(source) {
705
791
  let next = source;
706
792
  let hits = 0;
@@ -711,10 +797,10 @@ function rewriteComponentsCss(source) {
711
797
  });
712
798
  }
713
799
  for (const name of COMPONENTS_TOKEN_DEPRECATED) {
714
- const stillUsed = new RegExp(`var\\(\\s*${name.replace(/-/g, "\\-")}\\s*`, "i").test(next);
800
+ const stillUsed = new RegExp(`var\\(\\s*${name.replace(/-/g, "\\-")}(?![\\w-])`, "i").test(next);
715
801
  if (stillUsed)
716
802
  continue;
717
- const rem = removeDecl2(next, name);
803
+ const rem = removeCssDecl(next, name);
718
804
  next = rem.next;
719
805
  hits += rem.n;
720
806
  }
@@ -866,13 +952,14 @@ async function migrateComponents(cwd, dry) {
866
952
  const changes = [];
867
953
  const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r));
868
954
  for (const file of files) {
869
- const raw = readFileSync10(file, "utf8");
955
+ const raw = readFileSync11(file, "utf8");
870
956
  let next = raw;
871
957
  let hits = 0;
872
958
  if (file.endsWith(".css")) {
873
959
  const r = rewriteComponentsCss(raw);
874
- next = r.next;
875
- hits = r.hits;
960
+ const clean = stripOrphanColorMix(r.next);
961
+ next = clean.next;
962
+ hits = r.hits + clean.hits;
876
963
  } else if (/\.(tsx?|jsx?|mdx?)$/.test(file)) {
877
964
  const r = rewriteComponentsTsx(raw);
878
965
  next = fixStackDirectionSpacing(r.next);
@@ -907,6 +994,7 @@ var VAR_REPLACEMENTS2, COMPONENTS_TOKEN_DEPRECATED, DEPRECATED_VAR_RE2, DEPRECAT
907
994
  var init_components = __esm(() => {
908
995
  init_pkg();
909
996
  init_walk();
997
+ init_css_sanitize();
910
998
  VAR_REPLACEMENTS2 = [
911
999
  [/var\(\s*--layout-background\s*(?:,[^)]+)?\)/gi, "var(--shell-background-muted)"],
912
1000
  [/var\(\s*--header-background\s*(?:,[^)]+)?\)/gi, "var(--shell-background)"],
@@ -955,7 +1043,14 @@ var init_components = __esm(() => {
955
1043
  [/var\(\s*--item-radius\s*(?:,[^)]+)?\)/gi, "var(--shell-radius)"],
956
1044
  [/var\(\s*--block-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap)"],
957
1045
  [/var\(\s*--row-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap)"],
958
- [/var\(\s*--column-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap)"]
1046
+ [/var\(\s*--column-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap)"],
1047
+ [/var\(\s*--page-gap\s*(?:,[^)]+)?\)/gi, "var(--shell-gap)"],
1048
+ [/var\(\s*--page-pad\s*(?:,[^)]+)?\)/gi, "var(--shell-padding)"],
1049
+ [/var\(\s*--page-pad-x\s*(?:,[^)]+)?\)/gi, "var(--shell-padding-inline)"],
1050
+ [/var\(\s*--page-pad-y\s*(?:,[^)]+)?\)/gi, "var(--shell-padding-block)"],
1051
+ [/var\(\s*--card-radius\s*(?:,[^)]+)?\)/gi, "var(--shell-radius)"],
1052
+ [/var\(\s*--section-description-size\s*(?:,[^)]+)?\)/gi, "0.95rem"],
1053
+ [/var\(\s*--section-title-size\s*(?:,[^)]+)?\)/gi, "1.5rem"]
959
1054
  ];
960
1055
  COMPONENTS_TOKEN_DEPRECATED = [
961
1056
  "--layout-background",
@@ -1067,10 +1162,13 @@ var init_components = __esm(() => {
1067
1162
  "--empty-state-actions-margin-top",
1068
1163
  "--radio-group-gap",
1069
1164
  "--page-pad",
1070
- "--page-gap"
1165
+ "--page-pad-x",
1166
+ "--page-pad-y",
1167
+ "--page-gap",
1168
+ "--card-radius"
1071
1169
  ];
1072
- DEPRECATED_VAR_RE2 = new RegExp(`var\\(\\s*(?:${COMPONENTS_TOKEN_DEPRECATED.map((n) => n.replace(/-/g, "\\-")).join("|")})\\s*`, "i");
1073
- DEPRECATED_DECL_RE2 = new RegExp(`(?:${COMPONENTS_TOKEN_DEPRECATED.map((n) => n.replace(/-/g, "\\-")).join("|")})\\s*:`);
1170
+ DEPRECATED_VAR_RE2 = new RegExp(`var\\(\\s*(?:${COMPONENTS_TOKEN_DEPRECATED.map((n) => n.replace(/-/g, "\\-")).join("|")})(?![\\w-])`, "i");
1171
+ DEPRECATED_DECL_RE2 = new RegExp(`(?:${COMPONENTS_TOKEN_DEPRECATED.map((n) => n.replace(/-/g, "\\-")).join("|")})(?![\\w-])\\s*:`);
1074
1172
  });
1075
1173
 
1076
1174
  // src/lib/migrate/front.ts
@@ -1100,6 +1198,7 @@ var init_front = __esm(() => {
1100
1198
  init_form_aliases();
1101
1199
  init_form_tokens();
1102
1200
  init_components();
1201
+ init_css_sanitize();
1103
1202
  FRONT_MIGRATIONS = {
1104
1203
  "size-sml": migrateSizeSml,
1105
1204
  "field-border": migrateFieldBorder,
@@ -1107,6 +1206,7 @@ var init_front = __esm(() => {
1107
1206
  "form-aliases": migrateFormAliases,
1108
1207
  "form-tokens": migrateFormTokens,
1109
1208
  components: migrateComponents,
1209
+ "css-sanitize": migrateCssSanitize,
1110
1210
  "legacy-hacks": migrateLegacyReactHack,
1111
1211
  "socket-server": migrateSocketServer
1112
1212
  };
@@ -1114,6 +1214,7 @@ var init_front = __esm(() => {
1114
1214
  "form-aliases",
1115
1215
  "form-tokens",
1116
1216
  "components",
1217
+ "css-sanitize",
1117
1218
  "size-sml",
1118
1219
  "field-border",
1119
1220
  "modal-create",
@@ -1122,7 +1223,7 @@ var init_front = __esm(() => {
1122
1223
  });
1123
1224
 
1124
1225
  // src/index.ts
1125
- import { readFileSync as readFileSync20 } from "fs";
1226
+ import { readFileSync as readFileSync21 } from "fs";
1126
1227
  import { dirname as dirname4, join as join18 } from "path";
1127
1228
  import { fileURLToPath as fileURLToPath2 } from "url";
1128
1229
 
@@ -1242,6 +1343,17 @@ function installDeps(cwd = process.cwd()) {
1242
1343
  });
1243
1344
  return r.status === 0;
1244
1345
  }
1346
+ function installGlobalElcrm() {
1347
+ const bun = spawnSync("bun", ["--version"], { encoding: "utf8" });
1348
+ const cmd = bun.status === 0 ? "bun" : "npm";
1349
+ const args = ["i", "-g", "elcrm@latest"];
1350
+ console.log(`CLI: ${cmd} ${args.join(" ")}`);
1351
+ const r = spawnSync(cmd, args, {
1352
+ stdio: "inherit",
1353
+ env: process.env
1354
+ });
1355
+ return r.status === 0;
1356
+ }
1245
1357
 
1246
1358
  // src/commands/update.ts
1247
1359
  init_pkg();
@@ -1265,6 +1377,7 @@ var c = {
1265
1377
 
1266
1378
  // src/commands/update.ts
1267
1379
  init_front();
1380
+ init_css_sanitize();
1268
1381
 
1269
1382
  // src/lib/cssSync.ts
1270
1383
  init_form_tokens();
@@ -1272,7 +1385,7 @@ init_components();
1272
1385
  import {
1273
1386
  existsSync as existsSync6,
1274
1387
  readdirSync as readdirSync3,
1275
- readFileSync as readFileSync11,
1388
+ readFileSync as readFileSync12,
1276
1389
  statSync as statSync3,
1277
1390
  writeFileSync as writeFileSync4
1278
1391
  } from "fs";
@@ -1401,7 +1514,7 @@ function resolvePkgRoot(pkgName, cwd) {
1401
1514
  }
1402
1515
  function readPkgJson(dir) {
1403
1516
  try {
1404
- return JSON.parse(readFileSync11(join6(dir, "package.json"), "utf8"));
1517
+ return JSON.parse(readFileSync12(join6(dir, "package.json"), "utf8"));
1405
1518
  } catch {
1406
1519
  return null;
1407
1520
  }
@@ -1425,18 +1538,50 @@ function inspectPkgCss(pkgName, cwd) {
1425
1538
  return null;
1426
1539
  const pkg = readPkgJson(root);
1427
1540
  const exp = pkg?.exports;
1428
- const styleRel = exportPath(exp, "./style.css");
1429
- const lightRel = exportPath(exp, "./light.css");
1430
- const darkRel = exportPath(exp, "./dark.css");
1431
1541
  const abs = (rel2) => rel2 ? join6(root, rel2.replace(/^\.\//, "")) : undefined;
1542
+ const fileIfExists = (key) => {
1543
+ const rel2 = exportPath(exp, key);
1544
+ const a = abs(rel2);
1545
+ return a && existsSync6(a) ? a : undefined;
1546
+ };
1432
1547
  return {
1433
1548
  name: pkgName,
1434
1549
  root,
1435
- style: abs(styleRel) && existsSync6(abs(styleRel)) ? abs(styleRel) : undefined,
1436
- light: abs(lightRel) && existsSync6(abs(lightRel)) ? abs(lightRel) : undefined,
1437
- dark: abs(darkRel) && existsSync6(abs(darkRel)) ? abs(darkRel) : undefined
1550
+ style: fileIfExists("./style.css"),
1551
+ light: fileIfExists("./light.css"),
1552
+ dark: fileIfExists("./dark.css"),
1553
+ tokens: fileIfExists("./tokens.css")
1438
1554
  };
1439
1555
  }
1556
+ function resolveElcrmCssImport(spec, cwd) {
1557
+ const bare = spec.replace(/^["']|["']$/g, "").trim();
1558
+ const m = bare.match(/^(@elcrm\/[^/]+)\/(.+)$/);
1559
+ if (!m)
1560
+ return null;
1561
+ const pkgName = m[1];
1562
+ const sub = m[2];
1563
+ const root = resolvePkgRoot(pkgName, cwd);
1564
+ if (!root)
1565
+ return null;
1566
+ const pkg = readPkgJson(root);
1567
+ const rel2 = exportPath(pkg?.exports, `./${sub}`);
1568
+ if (!rel2)
1569
+ return null;
1570
+ const abs = join6(root, rel2.replace(/^\.\//, ""));
1571
+ return existsSync6(abs) ? abs : null;
1572
+ }
1573
+ function pruneBrokenElcrmImports(css, cwd) {
1574
+ const removed = [];
1575
+ const next = css.replace(/@import\s+["'](@elcrm\/[^"']+)["']\s*;[ \t]*(?:\r?\n)?/g, (full, spec) => {
1576
+ if (resolveElcrmCssImport(spec, cwd))
1577
+ return full;
1578
+ removed.push(spec);
1579
+ return "";
1580
+ });
1581
+ return { css: next.replace(/\n{3,}/g, `
1582
+
1583
+ `), removed };
1584
+ }
1440
1585
  function addDef(acc, seen, pkg, bucket, name, value) {
1441
1586
  const key = `${bucket}:${name}`;
1442
1587
  if (seen.has(key))
@@ -1457,7 +1602,7 @@ function collectPkgTokens(pkgName, cwd) {
1457
1602
  const acc = [];
1458
1603
  const seen = new Set;
1459
1604
  for (const file of files) {
1460
- const css = readFileSync11(file, "utf8");
1605
+ const css = readFileSync12(file, "utf8");
1461
1606
  const kind = fileKind(file);
1462
1607
  if (kind !== "mixed") {
1463
1608
  for (const [name, value] of parseCustomProps(css)) {
@@ -1619,13 +1764,14 @@ function syncElcrmCss(options) {
1619
1764
  "theme-dark.css": []
1620
1765
  };
1621
1766
  const imports = [];
1767
+ let removedImports = [];
1622
1768
  const fileFor = (bucket) => bucket === "geometry" ? paths.geometry : bucket === "light" ? paths.light : paths.dark;
1623
1769
  const labelFor = (bucket) => bucket === "geometry" ? "theme.css" : bucket === "light" ? "theme-light.css" : "theme-dark.css";
1624
1770
  for (const bucket of ["geometry", "light", "dark"]) {
1625
1771
  const file = fileFor(bucket);
1626
1772
  if (!existsSync6(file))
1627
1773
  continue;
1628
- let css = readFileSync11(file, "utf8");
1774
+ let css = readFileSync12(file, "utf8");
1629
1775
  const have = existingTokenNames(css);
1630
1776
  const missing = allDefs.filter((d) => d.bucket === bucket && !have.has(d.name) && !TOKEN_DEPRECATED.has(d.name));
1631
1777
  const uniq = [];
@@ -1650,15 +1796,17 @@ function syncElcrmCss(options) {
1650
1796
  writeFileSync4(file, next, "utf8");
1651
1797
  }
1652
1798
  if (existsSync6(paths.elcrm)) {
1653
- let css = readFileSync11(paths.elcrm, "utf8");
1799
+ let css = readFileSync12(paths.elcrm, "utf8");
1800
+ const pruned = pruneBrokenElcrmImports(css, options.cwd);
1801
+ css = pruned.css;
1802
+ removedImports = pruned.removed;
1654
1803
  for (const exp of cssExports) {
1655
1804
  const specs = [];
1656
1805
  if (exp.light)
1657
1806
  specs.push(`${exp.name}/light.css`);
1658
1807
  if (exp.dark)
1659
1808
  specs.push(`${exp.name}/dark.css`);
1660
- const tokens = join6(exp.root, "dist/tokens.css");
1661
- if (existsSync6(tokens))
1809
+ if (exp.tokens)
1662
1810
  specs.push(`${exp.name}/tokens.css`);
1663
1811
  for (const spec of specs) {
1664
1812
  const r = ensureImport(css, spec);
@@ -1667,17 +1815,18 @@ function syncElcrmCss(options) {
1667
1815
  imports.push(spec);
1668
1816
  }
1669
1817
  }
1670
- if (imports.length && !options.dry)
1818
+ if ((imports.length || removedImports.length) && !options.dry) {
1671
1819
  writeFileSync4(paths.elcrm, css, "utf8");
1820
+ }
1672
1821
  }
1673
- return { styleDir, added, imports, skippedPkgs };
1822
+ return { styleDir, added, imports, removedImports, skippedPkgs };
1674
1823
  }
1675
1824
 
1676
1825
  // src/commands/test.ts
1677
1826
  import { resolve } from "path";
1678
1827
 
1679
1828
  // src/lib/test/detect.ts
1680
- import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
1829
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
1681
1830
  import { join as join7 } from "path";
1682
1831
  function hasElcrmServer(pkg) {
1683
1832
  for (const section of [
@@ -1732,17 +1881,18 @@ function readPkgAt(dir) {
1732
1881
  if (!existsSync7(path))
1733
1882
  return null;
1734
1883
  try {
1735
- return JSON.parse(readFileSync12(path, "utf8"));
1884
+ return JSON.parse(readFileSync13(path, "utf8"));
1736
1885
  } catch {
1737
1886
  return null;
1738
1887
  }
1739
1888
  }
1740
1889
 
1741
1890
  // src/lib/test/front.ts
1742
- import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync5 } from "fs";
1891
+ import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync14, statSync as statSync5 } from "fs";
1743
1892
  import { join as join8, relative as relative2 } from "path";
1744
1893
  init_form_tokens();
1745
1894
  init_components();
1895
+ init_css_sanitize();
1746
1896
  var SKIP = new Set(["node_modules", "dist", ".git", "coverage", ".bun"]);
1747
1897
  function isThemeTokenFile(rel2) {
1748
1898
  const n = rel2.replace(/\\/g, "/");
@@ -1887,7 +2037,7 @@ function collectFrontIssues(cwd, pkg) {
1887
2037
  files.push(inSrc);
1888
2038
  }
1889
2039
  for (const file of files) {
1890
- const text = readFileSync13(file, "utf8");
2040
+ const text = readFileSync14(file, "utf8");
1891
2041
  const rel2 = relative2(cwd, file);
1892
2042
  const skipHardColor = isLib || isThemeTokenFile(rel2) || isGeneratedIcons(rel2);
1893
2043
  if (DEPRECATED_SOCKET_IMPORT_RE.test(text)) {
@@ -2005,6 +2155,16 @@ function collectFrontIssues(cwd, pkg) {
2005
2155
  fixable: true
2006
2156
  });
2007
2157
  }
2158
+ if (lineHasOrphanColorMix(line)) {
2159
+ issues.push({
2160
+ level: "error",
2161
+ code: "css-orphan-mix",
2162
+ message: "\u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0439 \u0445\u0432\u043E\u0441\u0442 color-mix (in srgb\u2026) \u2014 elcrm migrate css-sanitize",
2163
+ file: rel2,
2164
+ line: i + 1,
2165
+ fixable: true
2166
+ });
2167
+ }
2008
2168
  if (/\b(Block|Row|Column)\b/.test(line) && /@elcrm\/components/.test(text) && /from\s+["']@elcrm\/components/.test(text)) {
2009
2169
  if (/import\s*\{[^}]*\b(Block|Row|Column)\b/.test(line) || /<\/?(?:Block|Row|Column)\b/.test(line)) {
2010
2170
  issues.push({
@@ -2044,7 +2204,7 @@ function collectFrontIssues(cwd, pkg) {
2044
2204
  return issues;
2045
2205
  }
2046
2206
  async function runPkgScript(cwd, script) {
2047
- const pkg = JSON.parse(readFileSync13(join8(cwd, "package.json"), "utf8"));
2207
+ const pkg = JSON.parse(readFileSync14(join8(cwd, "package.json"), "utf8"));
2048
2208
  if (!pkg.scripts?.[script])
2049
2209
  return { ran: false, ok: true };
2050
2210
  console.log(`\u2192 bun run ${script}`);
@@ -2087,7 +2247,7 @@ function printIssues(issues, title) {
2087
2247
  }
2088
2248
 
2089
2249
  // src/lib/test/serverScan.ts
2090
- import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
2250
+ import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
2091
2251
  import { join as join9, relative as relative3 } from "path";
2092
2252
 
2093
2253
  // src/lib/test/serverRules.ts
@@ -2489,7 +2649,7 @@ function scanServerProject(root) {
2489
2649
  walkFiles2(root, root, files);
2490
2650
  const findings = [];
2491
2651
  for (const file of files) {
2492
- const text = readFileSync14(file, "utf8");
2652
+ const text = readFileSync15(file, "utf8");
2493
2653
  if (file.endsWith("package.json")) {
2494
2654
  findings.push(...scanPackageJson(root, file, text));
2495
2655
  } else {
@@ -2599,6 +2759,17 @@ elcrm test: ok`));
2599
2759
  // src/commands/update.ts
2600
2760
  async function cmdUpdate(parsed) {
2601
2761
  const cwd = process.cwd();
2762
+ const dry = hasFlag(parsed, "dry");
2763
+ const useCore = hasFlag(parsed, "core");
2764
+ const noInstall = hasFlag(parsed, "no-install");
2765
+ const doFix = hasFlag(parsed, "fix");
2766
+ const doTest = hasFlag(parsed, "test");
2767
+ if (dry) {
2768
+ console.log(`${c.gray("[dry]")} bun/npm i -g elcrm@latest`);
2769
+ } else if (!installGlobalElcrm()) {
2770
+ console.error("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0439 elcrm.");
2771
+ process.exit(1);
2772
+ }
2602
2773
  const pkg = readPackageJson(cwd);
2603
2774
  if (!pkg) {
2604
2775
  console.error("package.json \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u043C \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0435.");
@@ -2609,11 +2780,6 @@ async function cmdUpdate(parsed) {
2609
2780
  console.log("\u0417\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439 @elcrm/* \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.");
2610
2781
  return;
2611
2782
  }
2612
- const dry = hasFlag(parsed, "dry");
2613
- const useCore = hasFlag(parsed, "core");
2614
- const noInstall = hasFlag(parsed, "no-install");
2615
- const doFix = hasFlag(parsed, "fix");
2616
- const doTest = hasFlag(parsed, "test");
2617
2783
  const names = uniqueElcrmNames(deps);
2618
2784
  console.log(`\u041D\u0430\u0439\u0434\u0435\u043D\u043E @elcrm/*: ${names.length}`);
2619
2785
  let versions;
@@ -2731,9 +2897,18 @@ package.json \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D (${changed}).`);
2731
2897
  if (css.imports.length) {
2732
2898
  console.log(` ${c.green("+")} elcrm.css @import: ${css.imports.join(", ")}`);
2733
2899
  }
2734
- if (added === 0 && css.imports.length === 0) {
2900
+ if (css.removedImports.length) {
2901
+ console.log(` ${c.red("\u2212")} elcrm.css \u0431\u0438\u0442\u044B\u0435 @import: ${css.removedImports.join(", ")}`);
2902
+ }
2903
+ if (added === 0 && css.imports.length === 0 && css.removedImports.length === 0) {
2735
2904
  console.log(" \u0422\u043E\u043A\u0435\u043D\u044B \u0443\u0436\u0435 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435.");
2736
2905
  }
2906
+ const cleaned = await migrateCssSanitize(cwd, false);
2907
+ if (cleaned.length) {
2908
+ console.log(" css-sanitize:");
2909
+ for (const ch of cleaned)
2910
+ console.log(` ${ch.file}: ${ch.detail}`);
2911
+ }
2737
2912
  } catch (e) {
2738
2913
  console.log(` ${c.gray("css sync \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D:")} ${e instanceof Error ? e.message : e}`);
2739
2914
  }
@@ -2758,7 +2933,7 @@ import {
2758
2933
  existsSync as existsSync11,
2759
2934
  mkdirSync,
2760
2935
  readdirSync as readdirSync7,
2761
- readFileSync as readFileSync15,
2936
+ readFileSync as readFileSync16,
2762
2937
  statSync as statSync7,
2763
2938
  writeFileSync as writeFileSync5
2764
2939
  } from "fs";
@@ -2802,7 +2977,7 @@ function isEmptyDir(dir) {
2802
2977
  function writePkgName(pkgPath, name) {
2803
2978
  if (!existsSync11(pkgPath))
2804
2979
  return;
2805
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf8"));
2980
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf8"));
2806
2981
  pkg.name = name;
2807
2982
  pkg.version = pkg.version ?? "0.0.0";
2808
2983
  writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + `
@@ -2969,7 +3144,7 @@ async function cmdMigrate(parsed) {
2969
3144
  console.log(dry ? `
2970
3145
  --dry: ${changes.length} \u043F\u043E\u0442\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u044B\u0445 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.` : `
2971
3146
  \u041F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E: ${changes.length}.`);
2972
- if (name === "front" || name === "ui" || name === "size-sml" || name === "form-tokens" || name === "components") {
3147
+ if (name === "front" || name === "ui" || name === "size-sml" || name === "form-tokens" || name === "components" || name === "css-sanitize") {
2973
3148
  console.log("\u0414\u0430\u043B\u044C\u0448\u0435: elcrm css && elcrm audit");
2974
3149
  }
2975
3150
  }
@@ -3164,7 +3339,7 @@ import { resolve as resolve3 } from "path";
3164
3339
  import {
3165
3340
  existsSync as existsSync13,
3166
3341
  readdirSync as readdirSync9,
3167
- readFileSync as readFileSync17,
3342
+ readFileSync as readFileSync18,
3168
3343
  rmSync,
3169
3344
  statSync as statSync8,
3170
3345
  writeFileSync as writeFileSync7
@@ -3172,7 +3347,7 @@ import {
3172
3347
  import { join as join13 } from "path";
3173
3348
 
3174
3349
  // src/vite/concat-css.ts
3175
- import { readdirSync as readdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
3350
+ import { readdirSync as readdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync6 } from "fs";
3176
3351
  import { join as join12 } from "path";
3177
3352
  var SKIP_CSS = new Set([
3178
3353
  "index.css",
@@ -3189,7 +3364,7 @@ function concatDistCss(options) {
3189
3364
  ...options.skip ?? []
3190
3365
  ]);
3191
3366
  const files = readdirSync8(options.distDir).filter((f) => f.endsWith(".css") && !skip.has(f)).sort();
3192
- const css = files.map((f) => readFileSync16(join12(options.distDir, f), "utf8")).join(`
3367
+ const css = files.map((f) => readFileSync17(join12(options.distDir, f), "utf8")).join(`
3193
3368
  `);
3194
3369
  const outPath = join12(options.distDir, outFile);
3195
3370
  writeFileSync6(outPath, css);
@@ -3220,7 +3395,7 @@ function postbuildLib(options) {
3220
3395
  const flat = join13(dist, `${name}.d.ts`);
3221
3396
  const src = existsSync13(impl) ? impl : existsSync13(nestedIndex) ? nestedIndex : null;
3222
3397
  if (src) {
3223
- const text = readFileSync17(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
3398
+ const text = readFileSync18(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
3224
3399
  writeFileSync7(flat, text);
3225
3400
  console.log(`[postbuild] types \u2192 ${name}.d.ts`);
3226
3401
  }
@@ -3239,6 +3414,7 @@ async function cmdPostbuild(parsed) {
3239
3414
 
3240
3415
  // src/commands/css.ts
3241
3416
  init_pkg();
3417
+ init_css_sanitize();
3242
3418
  async function cmdCss(parsed) {
3243
3419
  const cwd = parsed.options.dir || process.cwd();
3244
3420
  const pkg = readPackageJson(cwd);
@@ -3272,14 +3448,28 @@ async function cmdCss(parsed) {
3272
3448
  for (const spec of result.imports)
3273
3449
  console.log(` ${c.dim(spec)}`);
3274
3450
  }
3451
+ if (result.removedImports.length) {
3452
+ console.log(` ${c.red("\u2212")} elcrm.css \u0431\u0438\u0442\u044B\u0435 @import:`);
3453
+ for (const spec of result.removedImports) {
3454
+ console.log(` ${c.dim(spec)}`);
3455
+ }
3456
+ }
3275
3457
  if (result.skippedPkgs.length) {
3276
3458
  console.log(` ${c.gray("\u0431\u0435\u0437 CSS-\u0442\u043E\u043A\u0435\u043D\u043E\u0432:")} ${result.skippedPkgs.join(", ")}`);
3277
3459
  }
3278
- if (total === 0 && result.imports.length === 0) {
3460
+ if (total === 0 && result.imports.length === 0 && result.removedImports.length === 0) {
3279
3461
  console.log("\u0412\u0441\u0451 \u0443\u0436\u0435 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435.");
3280
3462
  } else if (dry) {
3281
3463
  console.log("\u0417\u0430\u043F\u0438\u0441\u044C \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u043B\u0430\u0441\u044C (--dry).");
3282
3464
  }
3465
+ const cleaned = await migrateCssSanitize(cwd, dry);
3466
+ if (cleaned.length) {
3467
+ console.log(dry ? `
3468
+ [dry] css-sanitize: ${cleaned.length} \u0444\u0430\u0439\u043B(\u043E\u0432)` : `
3469
+ css-sanitize: \u043F\u043E\u0447\u0438\u043D\u0435\u043D\u043E ${cleaned.length} \u0444\u0430\u0439\u043B(\u043E\u0432)`);
3470
+ for (const ch of cleaned)
3471
+ console.log(` ${ch.file}: ${ch.detail}`);
3472
+ }
3283
3473
  } catch (e) {
3284
3474
  console.error(e instanceof Error ? e.message : e);
3285
3475
  process.exit(1);
@@ -3287,13 +3477,14 @@ async function cmdCss(parsed) {
3287
3477
  }
3288
3478
 
3289
3479
  // src/commands/audit.ts
3290
- import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
3480
+ import { existsSync as existsSync14, readFileSync as readFileSync19 } from "fs";
3291
3481
  import { join as join14, relative as relative4 } from "path";
3292
3482
  init_pkg();
3293
3483
  init_walk();
3294
3484
  init_front();
3295
3485
  init_form_tokens();
3296
3486
  init_components();
3487
+ init_css_sanitize();
3297
3488
  function scan(cwd) {
3298
3489
  const out = [];
3299
3490
  const pkg = readPackageJson(cwd);
@@ -3324,7 +3515,7 @@ function scan(cwd) {
3324
3515
  }
3325
3516
  const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r));
3326
3517
  for (const file of files) {
3327
- const text = readFileSync18(file, "utf8");
3518
+ const text = readFileSync19(file, "utf8");
3328
3519
  const rel2 = relative4(cwd, file);
3329
3520
  const lines = text.split(/\r?\n/);
3330
3521
  lines.forEach((line, i) => {
@@ -3406,7 +3597,7 @@ function scan(cwd) {
3406
3597
  return false;
3407
3598
  if (/theme(-light|-dark)?\.css$/.test(f))
3408
3599
  return false;
3409
- const t = readFileSync18(f, "utf8");
3600
+ const t = readFileSync19(f, "utf8");
3410
3601
  return t.includes("--search-border:") && t.includes("--button-secondary-bg:") && t.includes("transparent");
3411
3602
  });
3412
3603
  if (toolbarMarkers.length > 1) {
@@ -3426,6 +3617,21 @@ function scan(cwd) {
3426
3617
  fix: "elcrm css"
3427
3618
  });
3428
3619
  }
3620
+ if (styleDir && existsSync14(join14(styleDir, "elcrm.css"))) {
3621
+ const elcrmCss = readFileSync19(join14(styleDir, "elcrm.css"), "utf8");
3622
+ for (const m of elcrmCss.matchAll(/@import\s+["'](@elcrm\/[^"']+)["']/g)) {
3623
+ const spec = m[1];
3624
+ if (!resolveElcrmCssImport(spec, cwd)) {
3625
+ out.push({
3626
+ level: "error",
3627
+ code: "elcrm-import-missing",
3628
+ message: `\u0431\u0438\u0442\u044B\u0439 @import "${spec}" \u2014 \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442 \u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u043C \u043F\u0430\u043A\u0435\u0442\u0435`,
3629
+ file: relative4(cwd, join14(styleDir, "elcrm.css")),
3630
+ fix: "elcrm css"
3631
+ });
3632
+ }
3633
+ }
3634
+ }
3429
3635
  const depsForm = pkg.dependencies?.["@elcrm/form"] || pkg.devDependencies?.["@elcrm/form"];
3430
3636
  if (depsForm && styleDir) {
3431
3637
  const themeFiles = [
@@ -3433,7 +3639,7 @@ function scan(cwd) {
3433
3639
  "theme-dark.css",
3434
3640
  "theme.css"
3435
3641
  ].map((f) => join14(styleDir, f));
3436
- const themeText = themeFiles.filter(existsSync14).map((f) => readFileSync18(f, "utf8")).join(`
3642
+ const themeText = themeFiles.filter(existsSync14).map((f) => readFileSync19(f, "utf8")).join(`
3437
3643
  `);
3438
3644
  if (themeText && !/--popup-shadow\s*:/.test(themeText) && /--date-popup-shadow\s*:|--select-background\s*:/.test(themeText)) {
3439
3645
  out.push({
@@ -3455,7 +3661,7 @@ function scan(cwd) {
3455
3661
  for (const file of files) {
3456
3662
  if (!file.endsWith(".css"))
3457
3663
  continue;
3458
- const text = readFileSync18(file, "utf8");
3664
+ const text = readFileSync19(file, "utf8");
3459
3665
  const relPath = relative4(cwd, file);
3460
3666
  const lines = text.split(/\r?\n/);
3461
3667
  lines.forEach((line, i) => {
@@ -3481,6 +3687,16 @@ function scan(cwd) {
3481
3687
  fix: "elcrm migrate components"
3482
3688
  });
3483
3689
  }
3690
+ if (lineHasOrphanColorMix(line)) {
3691
+ out.push({
3692
+ level: "error",
3693
+ code: "css-orphan-mix",
3694
+ message: "\u0431\u0438\u0442\u044B\u0439 CSS: \u0445\u0432\u043E\u0441\u0442 color-mix \u0431\u0435\u0437 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430 (in srgb / transparent))",
3695
+ file: relPath,
3696
+ line: i + 1,
3697
+ fix: "elcrm migrate css-sanitize"
3698
+ });
3699
+ }
3484
3700
  });
3485
3701
  }
3486
3702
  return out;
@@ -3594,7 +3810,7 @@ import {
3594
3810
  existsSync as existsSync15,
3595
3811
  mkdirSync as mkdirSync2,
3596
3812
  readdirSync as readdirSync10,
3597
- readFileSync as readFileSync19,
3813
+ readFileSync as readFileSync20,
3598
3814
  writeFileSync as writeFileSync8
3599
3815
  } from "fs";
3600
3816
  import { dirname as dirname3, join as join15 } from "path";
@@ -3612,7 +3828,7 @@ function readBundled(relPath) {
3612
3828
  if (!existsSync15(full)) {
3613
3829
  throw new Error(`\u0428\u0430\u0431\u043B\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${relPath}`);
3614
3830
  }
3615
- return readFileSync19(full, "utf8");
3831
+ return readFileSync20(full, "utf8");
3616
3832
  }
3617
3833
 
3618
3834
  // src/commands/docs.ts
@@ -3682,23 +3898,6 @@ async function cmdCursor(parsed) {
3682
3898
  console.log(c.gray("\u0410\u0433\u0435\u043D\u0442: AGENTS.md + rules (\u0443\u0437\u043A\u0438\u0435 glob) + .cursor/docs. \u0421\u0434\u0430\u0447\u0430: elcrm test."));
3683
3899
  }
3684
3900
 
3685
- // src/commands/upd.ts
3686
- import { spawnSync as spawnSync2 } from "child_process";
3687
- async function cmdUpd() {
3688
- const bun = spawnSync2("bun", ["--version"], { encoding: "utf8" });
3689
- const cmd = bun.status === 0 ? "bun" : "npm";
3690
- const args = ["i", "-g", "elcrm@latest"];
3691
- console.log(`${c.cyan("\u2192")} ${cmd} ${args.join(" ")}`);
3692
- const r = spawnSync2(cmd, args, {
3693
- stdio: "inherit",
3694
- env: process.env
3695
- });
3696
- if (r.status !== 0) {
3697
- process.exit(r.status ?? 1);
3698
- }
3699
- console.log(c.green("CLI \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D. \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430: elcrm -v"));
3700
- }
3701
-
3702
3901
  // src/index.ts
3703
3902
  function cliVersion() {
3704
3903
  const here = dirname4(fileURLToPath2(import.meta.url));
@@ -3707,7 +3906,7 @@ function cliVersion() {
3707
3906
  join18(here, "package.json")
3708
3907
  ]) {
3709
3908
  try {
3710
- const v = JSON.parse(readFileSync20(p, "utf8")).version;
3909
+ const v = JSON.parse(readFileSync21(p, "utf8")).version;
3711
3910
  if (typeof v === "string" && v)
3712
3911
  return v;
3713
3912
  } catch {}
@@ -3720,93 +3919,57 @@ function showVersion() {
3720
3919
  function showHelp() {
3721
3920
  console.log(`elcrm \u2014 CLI \u0434\u043B\u044F \u044D\u043A\u043E\u0441\u0438\u0441\u0442\u0435\u043C\u044B @elcrm/*
3722
3921
 
3723
- \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435:
3724
- elcrm <\u043A\u043E\u043C\u0430\u043D\u0434\u0430> [\u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u044B] [\u0444\u043B\u0430\u0433\u0438]
3922
+ \u0415\u0436\u0435\u0434\u043D\u0435\u0432\u043D\u044B\u0439 \u0446\u0438\u043A\u043B:
3923
+ elcrm update --fix --test
3924
+ elcrm css
3925
+ elcrm docs
3926
+ elcrm cursor
3725
3927
 
3726
3928
  \u041A\u043E\u043C\u0430\u043D\u0434\u044B:
3727
- upd
3728
- \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0430\u043C CLI: bun i -g elcrm@latest (\u0431\u0435\u0437 bun \u2014 npm)
3729
-
3730
3929
  update [--dry] [--core] [--no-install] [--fix] [--test]
3731
- \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0432\u0441\u0435 @elcrm/* \u0434\u043E latest; --fix = migrate front; --test = elcrm test
3732
- \u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F: elcrm update --fix --test
3930
+ bun i -g elcrm@latest + @elcrm/* \u2192 install
3931
+ --fix = migrate front + sync CSS (\u0442\u043E\u043A\u0435\u043D\u044B, \u2212\u0431\u0438\u0442\u044B\u0435 @import) + css-sanitize
3932
+ --test = elcrm test
3733
3933
 
3734
- create <name> [--template app|lib|panel] [--no-install]
3735
- \u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442 \u0438\u0437 \u0448\u0430\u0431\u043B\u043E\u043D\u0430 (\u043A\u0430\u043A create-react-app)
3736
- panel \u2192 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438 web/ + api/ + bun run dev
3934
+ css [--dry] [--dir=\u2026]
3935
+ \u0422\u043E\u043A\u0435\u043D\u044B \u2192 theme*.css; elcrm.css: +\u0440\u0430\u0431\u043E\u0447\u0438\u0435 @import, \u2212\u0431\u0438\u0442\u044B\u0435 (\u043D\u0435\u0442 \u0444\u0430\u0439\u043B\u0430 \u0432 \u043F\u0430\u043A\u0435\u0442\u0435)
3936
+ \u0417\u0430\u0442\u0435\u043C css-sanitize (\u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0439 color-mix)
3737
3937
 
3738
- init [--template app|lib|panel] [--force] [--no-install]
3739
- \u0418\u043D\u0438\u0446\u0438\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0448\u0430\u0431\u043B\u043E\u043D\u043E\u043C
3938
+ docs [--dry] [--keep] [--dir=\u2026] [--lib]
3939
+ docs/*.elCRM.md (\u0448\u043F\u0430\u0440\u0433\u0430\u043B\u043A\u0438 \u043F\u0430\u043A\u0435\u0442\u043E\u0432)
3740
3940
 
3741
- migrate <name> [--dry]
3742
- migrate --list
3743
- \u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438: front|ui, form-aliases, form-tokens, components, size-sml, field-border, modal-create, legacy-hacks, socket-server
3941
+ cursor [--dry] [--keep] [--dir=\u2026] [--lib|-lib]
3942
+ .cursor/rules + .cursor/docs + AGENTS.md
3943
+ --lib: \u0447\u0435\u043A\u043B\u0438\u0441\u0442 \u0430\u0432\u0442\u043E\u0440\u0430 @elcrm/*
3944
+
3945
+ migrate <name> [--dry] | migrate --list
3946
+ front|ui = \u043F\u0430\u043A\u0435\u0442 \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0439 (aliases, tokens, components, css-sanitize, size, \u2026)
3947
+ \u043F\u043E \u043E\u0434\u043D\u043E\u0439: form-aliases, form-tokens, components, css-sanitize, size-sml,
3948
+ field-border, modal-create, legacy-hacks, socket-server
3744
3949
 
3745
- audit [--fix] [--dry] [--dir=\u2026]
3746
- \u0423\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 API + CSS-\u0442\u043E\u043A\u0435\u043D\u044B form (padding-\u0430\u043B\u0438\u0430\u0441\u044B, popup-shadow, border shorthand)
3747
- --fix \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 migrate front
3950
+ audit [--fix] [--dry] [--dir=\u2026]
3951
+ \u0421\u0442\u0430\u0440\u044B\u0435 API, \u0431\u0438\u0442\u044B\u0435 @import, orphan color-mix; --fix = migrate front
3952
+ \u041F\u043E\u0441\u043B\u0435 --fix \u0434\u043B\u044F imports: elcrm css
3748
3953
 
3749
3954
  doctor [--fix]
3750
- \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442 \u0438 \u043F\u0440\u0438 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E\u0441\u0442\u0438 \u043F\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043A \u0441\u0442\u0430\u043D\u0434\u0430\u0440\u0442\u0443
3955
+ \u0421\u0442\u0430\u043D\u0434\u0430\u0440\u0442\u044B; \u043D\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 --fix \u2192 bunx elcrm-server update
3751
3956
 
3752
3957
  test [dir] [--front|--server] [--no-scripts]
3753
- \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438: @elcrm/server \u0432 package.json \u2192 server, \u0438\u043D\u0430\u0447\u0435 \u2192 front
3754
- (workspaces \u2014 \u043A\u0430\u0436\u0434\u044B\u0439 \u043F\u0430\u043A\u0435\u0442 \u0441\u0432\u043E\u0438\u043C \u0440\u0435\u0436\u0438\u043C\u043E\u043C)
3958
+ \u0421\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u0430\u043D\u0430\u043B\u0438\u0437 + scripts \u043F\u0440\u043E\u0435\u043A\u0442\u0430
3755
3959
 
3960
+ create <name> [--template app|lib|panel] [--no-install]
3961
+ init [--template app|lib|panel] [--force] [--no-install]
3756
3962
  build [--client] [--url=\u2026] [--name=\u2026] [--dir=\u2026] [--folder=\u2026]
3757
- \u0421\u0431\u043E\u0440\u043A\u0430 \u0438 \u0434\u0435\u043F\u043B\u043E\u0439 (\u0438\u0437 @elcrm/deploy)
3758
-
3759
3963
  postbuild [--dir=./dist]
3760
- \u041F\u043E\u0441\u043B\u0435 vite build lib-\u043F\u0430\u043A\u0435\u0442\u0430: \u0441\u043A\u043B\u0435\u0438\u0442\u044C CSS + \u0441\u043F\u043B\u044E\u0449\u0438\u0442\u044C .d.ts
3761
- (package.json: "build": "vite build && elcrm postbuild")
3762
-
3763
- css [--dry] [--dir=\u2026]
3764
- \u0414\u0435\u0444\u043E\u043B\u0442\u043D\u044B\u0435 \u0442\u043E\u043A\u0435\u043D\u044B @elcrm/* \u2192 src/style/theme.css, theme-light/dark, elcrm.css
3765
- \u0423\u0436\u0435 \u0437\u0430\u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u043D\u0435 \u0437\u0430\u0442\u0438\u0440\u0430\u044E\u0442\u0441\u044F, \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u044E\u0449\u0438\u0435 (\u0433\u0440\u0443\u043F\u043F\u044B \u0447\u0435\u0440\u0435\u0437 \u043F\u0443\u0441\u0442\u0443\u044E \u0441\u0442\u0440\u043E\u043A\u0443)
3766
-
3767
- docs [--dry] [--keep] [--dir=\u2026] [--lib]
3768
- docs/*.elCRM.md \u043F\u043E \u043F\u0430\u043A\u0435\u0442\u0430\u043C (cli, api, alert, button, modal, notice, \u2026)
3769
- \u0441\u0435\u0440\u0432\u0435\u0440-only \u2192 SERVER, CLI, API, SOCKET; --lib \u0438\u043B\u0438 \u043F\u0430\u043A\u0435\u0442 @elcrm/* \u2192 \u0432\u0441\u0435 \u0444\u0430\u0439\u043B\u044B
3770
-
3771
- cursor [--dry] [--keep] [--dir=\u2026] [--lib|-lib]
3772
- .cursor/rules + .cursor/docs + AGENTS.md
3773
- --lib: \u0447\u0435\u043A\u043B\u0438\u0441\u0442 \u0430\u0432\u0442\u043E\u0440\u0430 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438 (\u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0430\u0432\u043E\u043A src \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C CLI-\u0448\u043F\u0430\u0440\u0433\u0430\u043B\u043A\u0438)
3774
-
3775
3964
  uuid
3776
- \u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C UUID
3777
-
3778
- help
3779
- \u042D\u0442\u0430 \u0441\u043F\u0440\u0430\u0432\u043A\u0430
3780
3965
 
3781
- \u0424\u043B\u0430\u0433\u0438:
3782
- -v, --version \u0412\u0435\u0440\u0441\u0438\u044F CLI
3783
- -h, --help \u0421\u043F\u0440\u0430\u0432\u043A\u0430
3966
+ \u0424\u043B\u0430\u0433\u0438: -v/--version, -h/--help
3784
3967
 
3785
3968
  \u041F\u0440\u0438\u043C\u0435\u0440\u044B:
3786
- elcrm -v
3787
- elcrm upd
3788
- elcrm update
3789
- elcrm update --dry
3790
- elcrm update --fix --test
3791
- elcrm migrate form-aliases --dry
3792
- elcrm migrate form-tokens --dry
3793
- elcrm create my-app --template app
3794
- elcrm init --template panel
3795
- elcrm init --template lib
3796
- elcrm migrate front --dry
3797
- elcrm migrate size-sml
3969
+ elcrm update --fix --test && elcrm css && elcrm docs && elcrm cursor
3970
+ elcrm css # ENOENT @elcrm/\u2026/light.css \u2192 \u0441\u043D\u0438\u043C\u0435\u0442 \u0431\u0438\u0442\u044B\u0439 import
3798
3971
  elcrm audit
3799
- elcrm audit --fix
3800
- elcrm doctor --fix
3801
- elcrm test
3802
- elcrm test --server
3803
- elcrm build --client --url=https://example.com/deploy
3804
- elcrm postbuild
3805
- elcrm css
3806
- elcrm css --dry
3807
- elcrm docs
3808
- elcrm cursor
3809
- elcrm cursor --lib
3972
+ elcrm migrate front --dry
3810
3973
  `);
3811
3974
  }
3812
3975
  async function main() {
@@ -3823,9 +3986,6 @@ async function main() {
3823
3986
  console.log("parseArgs:", JSON.stringify(parsed, null, 2));
3824
3987
  }
3825
3988
  switch (parsed.command) {
3826
- case "upd":
3827
- await cmdUpd();
3828
- break;
3829
3989
  case "update":
3830
3990
  await cmdUpdate(parsed);
3831
3991
  break;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elcrm",
3
- "version": "1.1.3",
4
- "description": "CLI @elcrm/*: update, create, migrate front/components, audit, doctor, test, build, css",
3
+ "version": "1.1.5",
4
+ "description": "CLI @elcrm/*: update --fix --test, css (prune imports), docs, cursor, migrate, audit",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "MaSkal <dev@elcrm.online>",
@@ -13,7 +13,7 @@ alwaysApply: true
13
13
 
14
14
  | Файл | Пакет | Делать так |
15
15
  | ------------------------ | ---------------------- | ------------------------------------------------------- |
16
- | `CLI.elCRM.md` | `elcrm` | `upd` (сам CLI), `update --fix --test`, docs, cursor |
16
+ | `CLI.elCRM.md` | `elcrm` | `update --fix --test` (CLI + пакеты), docs, cursor |
17
17
  | `API.elCRM.md` | `@elcrm/api` | `Api.create` + `Api.query("router/method", body)` |
18
18
  | `ALERT.elCRM.md` | `@elcrm/alert` | `<Alert.Init />`, `Alert.Send` / `Confirm` |
19
19
  | `BUTTON.elCRM.md` | `@elcrm/button` | `<Button onSend>`, size s/m/l, не `<button>` |
@@ -1,8 +1,12 @@
1
- <!-- Сгенерировано `elcrm docs`. Пакет: elcrm (CLI). -->
1
+ <!-- Источник: elCRM.cli/templates/elcrm-docs/CLI.elCRM.md. Обновить: elcrm docs. -->
2
2
 
3
3
  # `elcrm` CLI
4
4
 
5
- Глобально: `bun i -g elcrm` или `elcrm upd` (latest). В приложении — после смены `@elcrm/*`:
5
+ Глобально: `bun i -g elcrm` (нужна **свежая** версия: `elcrm -v`).
6
+
7
+ ## Ежедневный цикл (запомнить эти 4)
8
+
9
+ После смены `@elcrm/*` или если «что-то сломалось» в UI/CSS:
6
10
 
7
11
  ```bash
8
12
  elcrm update --fix --test
@@ -11,18 +15,102 @@ elcrm docs
11
15
  elcrm cursor
12
16
  ```
13
17
 
14
- | Команда | Зачем |
18
+ | # | Команда | Что делает |
19
+ | --- | --- | --- |
20
+ | 1 | `elcrm update --fix --test` | Обновляет глобальный `elcrm` + все `@elcrm/*` в `package.json` → install → **migrate front** (API/токены/css-sanitize) → внутри `--fix` ещё раз **sync CSS** → `elcrm test` |
21
+ | 2 | `elcrm css` | Недостающие CSS-токены → `theme.css` / `theme-light` / `theme-dark`. В `elcrm.css`: **добавляет** рабочие `@import`, **удаляет битые** (нет export/файла в установленном пакете). Затем css-sanitize |
22
+ | 3 | `elcrm docs` | Перезаписывает `docs/*.elCRM.md` (шпаргалки пакетов) |
23
+ | 4 | `elcrm cursor` | `.cursor/rules` + `.cursor/docs` + `AGENTS.md` для агента |
24
+
25
+ Не чинит само по себе: ручные баги в компонентах, сервер без `elcrm-server`, секреты. Чинит: устаревшие API после миграций, битые `@import` в `elcrm.css`, осиротевший `color-mix`, недостающие токены.
26
+
27
+ ---
28
+
29
+ ## Все команды
30
+
31
+ ### `update` — зависимости
32
+
33
+ ```bash
34
+ elcrm update # latest @elcrm/* + bun i -g elcrm@latest
35
+ elcrm update --dry # только отчёт
36
+ elcrm update --core # версии как в @elcrm/core
37
+ elcrm update --no-install # только package.json
38
+ elcrm update --fix # + migrate front + sync CSS (+ prune битых @import)
39
+ elcrm update --test # + elcrm test (обычно вместе: --fix --test)
40
+ ```
41
+
42
+ ### `css` — стили приложения
43
+
44
+ ```bash
45
+ elcrm css # токены + починка elcrm.css
46
+ elcrm css --dry
47
+ elcrm css --dir=./web # panel: web/
48
+ ```
49
+
50
+ Почему Vite ругался на `@elcrm/search/light.css`: в `elcrm.css` остался `@import`, а в **установленном** `@elcrm/search@0.1.4` нет export `./light.css`. Раньше `css` только добавлял imports — теперь **снимает** такие строки. Лечение: `elcrm css` (или `update --fix`).
51
+
52
+ ### `docs` / `cursor` — шпаргалки
53
+
54
+ ```bash
55
+ elcrm docs # docs/*.elCRM.md
56
+ elcrm docs --dry | --keep | --lib | --dir=…
57
+ elcrm cursor # .cursor/rules + .cursor/docs + AGENTS.md
58
+ elcrm cursor --lib # + чеклист автора @elcrm/* (правило elcrm-lib)
59
+ ```
60
+
61
+ ### `migrate` — точечные правки кода
62
+
63
+ ```bash
64
+ elcrm migrate --list
65
+ elcrm migrate front # пакет (то же, что update/audit/doctor --fix):
66
+ # form-aliases → form-tokens → components →
67
+ # css-sanitize → size-sml → field-border →
68
+ # modal-create → socket-server
69
+ elcrm migrate css-sanitize # осиротевшие in srgb / transparent);
70
+ elcrm migrate components # Block/Row → Stack, --page-* → --shell-*
71
+ elcrm migrate form-tokens
72
+ elcrm migrate form-aliases
73
+ elcrm migrate size-sml # size sm|md → s|m|l
74
+ elcrm migrate field-border
75
+ elcrm migrate modal-create # Modal.Add → Create
76
+ elcrm migrate legacy-hacks # только по имени, не в front
77
+ elcrm migrate socket-server
78
+ ```
79
+
80
+ Флаг у всех: `--dry`.
81
+
82
+ ### `audit` / `doctor` / `test` — проверки
83
+
84
+ ```bash
85
+ elcrm audit # старые API, битые @import, orphan color-mix, …
86
+ elcrm audit --fix # = migrate front (+ css не вызывает — после: elcrm css)
87
+ elcrm doctor # стандарты проекта
88
+ elcrm doctor --fix # type/engines + migrate front
89
+ elcrm test # статический анализ + scripts проекта
90
+ elcrm test --front|--server|--no-scripts
91
+ ```
92
+
93
+ ### Прочее
94
+
95
+ ```bash
96
+ elcrm create <name> [--template app|lib|panel] [--no-install]
97
+ elcrm init [--template …] [--force] [--no-install]
98
+ elcrm build --client [--url=…] [--name=…] [--dir=…] [--folder=…]
99
+ elcrm postbuild [--dir=./dist] # lib: склейка CSS после vite build
100
+ elcrm uuid
101
+ elcrm -v / --help
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Когда что запускать
107
+
108
+ | Ситуация | Команды |
15
109
  | --- | --- |
16
- | `upd` | сам CLI latest (`bun i -g elcrm@latest`) |
17
- | `update [--dry] [--core] [--fix] [--test]` | latest `@elcrm/*` в проекте; `--fix` = migrate front + css; `--test` = elcrm test |
18
- | `create` / `init` | шаблоны `app` \| `lib` \| `panel` |
19
- | `migrate <name> [--dry]` | `front` (пакет), `form-aliases`, `form-tokens`, `components`, `size-sml`, `field-border`, `modal-create`, `legacy-hacks`, `socket-server` |
20
- | `audit [--fix]` | старые API; `--fix` = migrate front |
21
- | `doctor [--fix]` | стандарты; сервер --fix → `bunx elcrm-server update` |
22
- | `test [--front\|--server] [--no-scripts]` | статический анализ + scripts |
23
- | `css [--dry]` | недостающие токены → theme*.css |
24
- | `docs` / `cursor` | шпаргалки и правила агента |
25
- | `cursor --lib` / `-lib` | правила автора `@elcrm/*` lib (чеклист после правок src) |
26
- | `build` / `postbuild` / `uuid` | деплой клиента/api; склейка CSS lib |
27
-
28
- Не выдумывать флаги — `elcrm --help` и `COMMANDS.md`.
110
+ | Обновили пакеты / «после elcrm update всё красное» | полный цикл из 4 команд сверху |
111
+ | Vite: `ENOENT @elcrm/…/light.css` | `elcrm css` при необходимости `elcrm audit` |
112
+ | Битый theme (`in srgb` без свойства) | `elcrm migrate css-sanitize` или `update --fix` |
113
+ | Агент врёт API | `elcrm docs && elcrm cursor` |
114
+ | Автор правит `@elcrm/*` src | чеклист: `elcrm cursor --lib`; шпаргалки править в **elCRM.cli** |
115
+
116
+ Не выдумывать флаги — `elcrm --help` и `COMMANDS.md` в репо CLI.