elcrm 1.1.3 → 1.1.4

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
  }
@@ -1457,7 +1570,7 @@ function collectPkgTokens(pkgName, cwd) {
1457
1570
  const acc = [];
1458
1571
  const seen = new Set;
1459
1572
  for (const file of files) {
1460
- const css = readFileSync11(file, "utf8");
1573
+ const css = readFileSync12(file, "utf8");
1461
1574
  const kind = fileKind(file);
1462
1575
  if (kind !== "mixed") {
1463
1576
  for (const [name, value] of parseCustomProps(css)) {
@@ -1625,7 +1738,7 @@ function syncElcrmCss(options) {
1625
1738
  const file = fileFor(bucket);
1626
1739
  if (!existsSync6(file))
1627
1740
  continue;
1628
- let css = readFileSync11(file, "utf8");
1741
+ let css = readFileSync12(file, "utf8");
1629
1742
  const have = existingTokenNames(css);
1630
1743
  const missing = allDefs.filter((d) => d.bucket === bucket && !have.has(d.name) && !TOKEN_DEPRECATED.has(d.name));
1631
1744
  const uniq = [];
@@ -1650,7 +1763,7 @@ function syncElcrmCss(options) {
1650
1763
  writeFileSync4(file, next, "utf8");
1651
1764
  }
1652
1765
  if (existsSync6(paths.elcrm)) {
1653
- let css = readFileSync11(paths.elcrm, "utf8");
1766
+ let css = readFileSync12(paths.elcrm, "utf8");
1654
1767
  for (const exp of cssExports) {
1655
1768
  const specs = [];
1656
1769
  if (exp.light)
@@ -1677,7 +1790,7 @@ function syncElcrmCss(options) {
1677
1790
  import { resolve } from "path";
1678
1791
 
1679
1792
  // src/lib/test/detect.ts
1680
- import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
1793
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
1681
1794
  import { join as join7 } from "path";
1682
1795
  function hasElcrmServer(pkg) {
1683
1796
  for (const section of [
@@ -1732,17 +1845,18 @@ function readPkgAt(dir) {
1732
1845
  if (!existsSync7(path))
1733
1846
  return null;
1734
1847
  try {
1735
- return JSON.parse(readFileSync12(path, "utf8"));
1848
+ return JSON.parse(readFileSync13(path, "utf8"));
1736
1849
  } catch {
1737
1850
  return null;
1738
1851
  }
1739
1852
  }
1740
1853
 
1741
1854
  // src/lib/test/front.ts
1742
- import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync5 } from "fs";
1855
+ import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync14, statSync as statSync5 } from "fs";
1743
1856
  import { join as join8, relative as relative2 } from "path";
1744
1857
  init_form_tokens();
1745
1858
  init_components();
1859
+ init_css_sanitize();
1746
1860
  var SKIP = new Set(["node_modules", "dist", ".git", "coverage", ".bun"]);
1747
1861
  function isThemeTokenFile(rel2) {
1748
1862
  const n = rel2.replace(/\\/g, "/");
@@ -1887,7 +2001,7 @@ function collectFrontIssues(cwd, pkg) {
1887
2001
  files.push(inSrc);
1888
2002
  }
1889
2003
  for (const file of files) {
1890
- const text = readFileSync13(file, "utf8");
2004
+ const text = readFileSync14(file, "utf8");
1891
2005
  const rel2 = relative2(cwd, file);
1892
2006
  const skipHardColor = isLib || isThemeTokenFile(rel2) || isGeneratedIcons(rel2);
1893
2007
  if (DEPRECATED_SOCKET_IMPORT_RE.test(text)) {
@@ -2005,6 +2119,16 @@ function collectFrontIssues(cwd, pkg) {
2005
2119
  fixable: true
2006
2120
  });
2007
2121
  }
2122
+ if (lineHasOrphanColorMix(line)) {
2123
+ issues.push({
2124
+ level: "error",
2125
+ code: "css-orphan-mix",
2126
+ 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",
2127
+ file: rel2,
2128
+ line: i + 1,
2129
+ fixable: true
2130
+ });
2131
+ }
2008
2132
  if (/\b(Block|Row|Column)\b/.test(line) && /@elcrm\/components/.test(text) && /from\s+["']@elcrm\/components/.test(text)) {
2009
2133
  if (/import\s*\{[^}]*\b(Block|Row|Column)\b/.test(line) || /<\/?(?:Block|Row|Column)\b/.test(line)) {
2010
2134
  issues.push({
@@ -2044,7 +2168,7 @@ function collectFrontIssues(cwd, pkg) {
2044
2168
  return issues;
2045
2169
  }
2046
2170
  async function runPkgScript(cwd, script) {
2047
- const pkg = JSON.parse(readFileSync13(join8(cwd, "package.json"), "utf8"));
2171
+ const pkg = JSON.parse(readFileSync14(join8(cwd, "package.json"), "utf8"));
2048
2172
  if (!pkg.scripts?.[script])
2049
2173
  return { ran: false, ok: true };
2050
2174
  console.log(`\u2192 bun run ${script}`);
@@ -2087,7 +2211,7 @@ function printIssues(issues, title) {
2087
2211
  }
2088
2212
 
2089
2213
  // src/lib/test/serverScan.ts
2090
- import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync6 } from "fs";
2214
+ import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
2091
2215
  import { join as join9, relative as relative3 } from "path";
2092
2216
 
2093
2217
  // src/lib/test/serverRules.ts
@@ -2489,7 +2613,7 @@ function scanServerProject(root) {
2489
2613
  walkFiles2(root, root, files);
2490
2614
  const findings = [];
2491
2615
  for (const file of files) {
2492
- const text = readFileSync14(file, "utf8");
2616
+ const text = readFileSync15(file, "utf8");
2493
2617
  if (file.endsWith("package.json")) {
2494
2618
  findings.push(...scanPackageJson(root, file, text));
2495
2619
  } else {
@@ -2599,6 +2723,17 @@ elcrm test: ok`));
2599
2723
  // src/commands/update.ts
2600
2724
  async function cmdUpdate(parsed) {
2601
2725
  const cwd = process.cwd();
2726
+ const dry = hasFlag(parsed, "dry");
2727
+ const useCore = hasFlag(parsed, "core");
2728
+ const noInstall = hasFlag(parsed, "no-install");
2729
+ const doFix = hasFlag(parsed, "fix");
2730
+ const doTest = hasFlag(parsed, "test");
2731
+ if (dry) {
2732
+ console.log(`${c.gray("[dry]")} bun/npm i -g elcrm@latest`);
2733
+ } else if (!installGlobalElcrm()) {
2734
+ 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.");
2735
+ process.exit(1);
2736
+ }
2602
2737
  const pkg = readPackageJson(cwd);
2603
2738
  if (!pkg) {
2604
2739
  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 +2744,6 @@ async function cmdUpdate(parsed) {
2609
2744
  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
2745
  return;
2611
2746
  }
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
2747
  const names = uniqueElcrmNames(deps);
2618
2748
  console.log(`\u041D\u0430\u0439\u0434\u0435\u043D\u043E @elcrm/*: ${names.length}`);
2619
2749
  let versions;
@@ -2734,6 +2864,12 @@ package.json \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D (${changed}).`);
2734
2864
  if (added === 0 && css.imports.length === 0) {
2735
2865
  console.log(" \u0422\u043E\u043A\u0435\u043D\u044B \u0443\u0436\u0435 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435.");
2736
2866
  }
2867
+ const cleaned = await migrateCssSanitize(cwd, false);
2868
+ if (cleaned.length) {
2869
+ console.log(" css-sanitize:");
2870
+ for (const ch of cleaned)
2871
+ console.log(` ${ch.file}: ${ch.detail}`);
2872
+ }
2737
2873
  } catch (e) {
2738
2874
  console.log(` ${c.gray("css sync \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D:")} ${e instanceof Error ? e.message : e}`);
2739
2875
  }
@@ -2758,7 +2894,7 @@ import {
2758
2894
  existsSync as existsSync11,
2759
2895
  mkdirSync,
2760
2896
  readdirSync as readdirSync7,
2761
- readFileSync as readFileSync15,
2897
+ readFileSync as readFileSync16,
2762
2898
  statSync as statSync7,
2763
2899
  writeFileSync as writeFileSync5
2764
2900
  } from "fs";
@@ -2802,7 +2938,7 @@ function isEmptyDir(dir) {
2802
2938
  function writePkgName(pkgPath, name) {
2803
2939
  if (!existsSync11(pkgPath))
2804
2940
  return;
2805
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf8"));
2941
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf8"));
2806
2942
  pkg.name = name;
2807
2943
  pkg.version = pkg.version ?? "0.0.0";
2808
2944
  writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + `
@@ -2969,7 +3105,7 @@ async function cmdMigrate(parsed) {
2969
3105
  console.log(dry ? `
2970
3106
  --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
3107
  \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") {
3108
+ if (name === "front" || name === "ui" || name === "size-sml" || name === "form-tokens" || name === "components" || name === "css-sanitize") {
2973
3109
  console.log("\u0414\u0430\u043B\u044C\u0448\u0435: elcrm css && elcrm audit");
2974
3110
  }
2975
3111
  }
@@ -3164,7 +3300,7 @@ import { resolve as resolve3 } from "path";
3164
3300
  import {
3165
3301
  existsSync as existsSync13,
3166
3302
  readdirSync as readdirSync9,
3167
- readFileSync as readFileSync17,
3303
+ readFileSync as readFileSync18,
3168
3304
  rmSync,
3169
3305
  statSync as statSync8,
3170
3306
  writeFileSync as writeFileSync7
@@ -3172,7 +3308,7 @@ import {
3172
3308
  import { join as join13 } from "path";
3173
3309
 
3174
3310
  // src/vite/concat-css.ts
3175
- import { readdirSync as readdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
3311
+ import { readdirSync as readdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync6 } from "fs";
3176
3312
  import { join as join12 } from "path";
3177
3313
  var SKIP_CSS = new Set([
3178
3314
  "index.css",
@@ -3189,7 +3325,7 @@ function concatDistCss(options) {
3189
3325
  ...options.skip ?? []
3190
3326
  ]);
3191
3327
  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(`
3328
+ const css = files.map((f) => readFileSync17(join12(options.distDir, f), "utf8")).join(`
3193
3329
  `);
3194
3330
  const outPath = join12(options.distDir, outFile);
3195
3331
  writeFileSync6(outPath, css);
@@ -3220,7 +3356,7 @@ function postbuildLib(options) {
3220
3356
  const flat = join13(dist, `${name}.d.ts`);
3221
3357
  const src = existsSync13(impl) ? impl : existsSync13(nestedIndex) ? nestedIndex : null;
3222
3358
  if (src) {
3223
- const text = readFileSync17(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
3359
+ const text = readFileSync18(src, "utf8").replace(/from ['"]\.\.\/([^'"]+)['"]/g, `from './$1'`);
3224
3360
  writeFileSync7(flat, text);
3225
3361
  console.log(`[postbuild] types \u2192 ${name}.d.ts`);
3226
3362
  }
@@ -3239,6 +3375,7 @@ async function cmdPostbuild(parsed) {
3239
3375
 
3240
3376
  // src/commands/css.ts
3241
3377
  init_pkg();
3378
+ init_css_sanitize();
3242
3379
  async function cmdCss(parsed) {
3243
3380
  const cwd = parsed.options.dir || process.cwd();
3244
3381
  const pkg = readPackageJson(cwd);
@@ -3280,6 +3417,14 @@ async function cmdCss(parsed) {
3280
3417
  } else if (dry) {
3281
3418
  console.log("\u0417\u0430\u043F\u0438\u0441\u044C \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u043B\u0430\u0441\u044C (--dry).");
3282
3419
  }
3420
+ const cleaned = await migrateCssSanitize(cwd, dry);
3421
+ if (cleaned.length) {
3422
+ console.log(dry ? `
3423
+ [dry] css-sanitize: ${cleaned.length} \u0444\u0430\u0439\u043B(\u043E\u0432)` : `
3424
+ css-sanitize: \u043F\u043E\u0447\u0438\u043D\u0435\u043D\u043E ${cleaned.length} \u0444\u0430\u0439\u043B(\u043E\u0432)`);
3425
+ for (const ch of cleaned)
3426
+ console.log(` ${ch.file}: ${ch.detail}`);
3427
+ }
3283
3428
  } catch (e) {
3284
3429
  console.error(e instanceof Error ? e.message : e);
3285
3430
  process.exit(1);
@@ -3287,13 +3432,14 @@ async function cmdCss(parsed) {
3287
3432
  }
3288
3433
 
3289
3434
  // src/commands/audit.ts
3290
- import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
3435
+ import { existsSync as existsSync14, readFileSync as readFileSync19 } from "fs";
3291
3436
  import { join as join14, relative as relative4 } from "path";
3292
3437
  init_pkg();
3293
3438
  init_walk();
3294
3439
  init_front();
3295
3440
  init_form_tokens();
3296
3441
  init_components();
3442
+ init_css_sanitize();
3297
3443
  function scan(cwd) {
3298
3444
  const out = [];
3299
3445
  const pkg = readPackageJson(cwd);
@@ -3324,7 +3470,7 @@ function scan(cwd) {
3324
3470
  }
3325
3471
  const files = projectCodeRoots(cwd).flatMap((r) => walkCodeFiles(r));
3326
3472
  for (const file of files) {
3327
- const text = readFileSync18(file, "utf8");
3473
+ const text = readFileSync19(file, "utf8");
3328
3474
  const rel2 = relative4(cwd, file);
3329
3475
  const lines = text.split(/\r?\n/);
3330
3476
  lines.forEach((line, i) => {
@@ -3406,7 +3552,7 @@ function scan(cwd) {
3406
3552
  return false;
3407
3553
  if (/theme(-light|-dark)?\.css$/.test(f))
3408
3554
  return false;
3409
- const t = readFileSync18(f, "utf8");
3555
+ const t = readFileSync19(f, "utf8");
3410
3556
  return t.includes("--search-border:") && t.includes("--button-secondary-bg:") && t.includes("transparent");
3411
3557
  });
3412
3558
  if (toolbarMarkers.length > 1) {
@@ -3433,7 +3579,7 @@ function scan(cwd) {
3433
3579
  "theme-dark.css",
3434
3580
  "theme.css"
3435
3581
  ].map((f) => join14(styleDir, f));
3436
- const themeText = themeFiles.filter(existsSync14).map((f) => readFileSync18(f, "utf8")).join(`
3582
+ const themeText = themeFiles.filter(existsSync14).map((f) => readFileSync19(f, "utf8")).join(`
3437
3583
  `);
3438
3584
  if (themeText && !/--popup-shadow\s*:/.test(themeText) && /--date-popup-shadow\s*:|--select-background\s*:/.test(themeText)) {
3439
3585
  out.push({
@@ -3455,7 +3601,7 @@ function scan(cwd) {
3455
3601
  for (const file of files) {
3456
3602
  if (!file.endsWith(".css"))
3457
3603
  continue;
3458
- const text = readFileSync18(file, "utf8");
3604
+ const text = readFileSync19(file, "utf8");
3459
3605
  const relPath = relative4(cwd, file);
3460
3606
  const lines = text.split(/\r?\n/);
3461
3607
  lines.forEach((line, i) => {
@@ -3481,6 +3627,16 @@ function scan(cwd) {
3481
3627
  fix: "elcrm migrate components"
3482
3628
  });
3483
3629
  }
3630
+ if (lineHasOrphanColorMix(line)) {
3631
+ out.push({
3632
+ level: "error",
3633
+ code: "css-orphan-mix",
3634
+ 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))",
3635
+ file: relPath,
3636
+ line: i + 1,
3637
+ fix: "elcrm migrate css-sanitize"
3638
+ });
3639
+ }
3484
3640
  });
3485
3641
  }
3486
3642
  return out;
@@ -3594,7 +3750,7 @@ import {
3594
3750
  existsSync as existsSync15,
3595
3751
  mkdirSync as mkdirSync2,
3596
3752
  readdirSync as readdirSync10,
3597
- readFileSync as readFileSync19,
3753
+ readFileSync as readFileSync20,
3598
3754
  writeFileSync as writeFileSync8
3599
3755
  } from "fs";
3600
3756
  import { dirname as dirname3, join as join15 } from "path";
@@ -3612,7 +3768,7 @@ function readBundled(relPath) {
3612
3768
  if (!existsSync15(full)) {
3613
3769
  throw new Error(`\u0428\u0430\u0431\u043B\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${relPath}`);
3614
3770
  }
3615
- return readFileSync19(full, "utf8");
3771
+ return readFileSync20(full, "utf8");
3616
3772
  }
3617
3773
 
3618
3774
  // src/commands/docs.ts
@@ -3682,23 +3838,6 @@ async function cmdCursor(parsed) {
3682
3838
  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
3839
  }
3684
3840
 
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
3841
  // src/index.ts
3703
3842
  function cliVersion() {
3704
3843
  const here = dirname4(fileURLToPath2(import.meta.url));
@@ -3707,7 +3846,7 @@ function cliVersion() {
3707
3846
  join18(here, "package.json")
3708
3847
  ]) {
3709
3848
  try {
3710
- const v = JSON.parse(readFileSync20(p, "utf8")).version;
3849
+ const v = JSON.parse(readFileSync21(p, "utf8")).version;
3711
3850
  if (typeof v === "string" && v)
3712
3851
  return v;
3713
3852
  } catch {}
@@ -3724,11 +3863,9 @@ function showHelp() {
3724
3863
  elcrm <\u043A\u043E\u043C\u0430\u043D\u0434\u0430> [\u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u044B] [\u0444\u043B\u0430\u0433\u0438]
3725
3864
 
3726
3865
  \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
3866
  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
3867
+ CLI (bun i -g elcrm@latest) + \u0432\u0441\u0435 @elcrm/* \u0432 \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u0434\u043E latest
3868
+ --fix = migrate front; --test = elcrm test
3732
3869
  \u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F: elcrm update --fix --test
3733
3870
 
3734
3871
  create <name> [--template app|lib|panel] [--no-install]
@@ -3740,7 +3877,7 @@ function showHelp() {
3740
3877
 
3741
3878
  migrate <name> [--dry]
3742
3879
  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
3880
+ \u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438: front|ui, form-aliases, form-tokens, components, css-sanitize, size-sml, field-border, modal-create, legacy-hacks, socket-server
3744
3881
 
3745
3882
  audit [--fix] [--dry] [--dir=\u2026]
3746
3883
  \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)
@@ -3784,7 +3921,6 @@ function showHelp() {
3784
3921
 
3785
3922
  \u041F\u0440\u0438\u043C\u0435\u0440\u044B:
3786
3923
  elcrm -v
3787
- elcrm upd
3788
3924
  elcrm update
3789
3925
  elcrm update --dry
3790
3926
  elcrm update --fix --test
@@ -3823,9 +3959,6 @@ async function main() {
3823
3959
  console.log("parseArgs:", JSON.stringify(parsed, null, 2));
3824
3960
  }
3825
3961
  switch (parsed.command) {
3826
- case "upd":
3827
- await cmdUpd();
3828
- break;
3829
3962
  case "update":
3830
3963
  await cmdUpdate(parsed);
3831
3964
  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.4",
4
+ "description": "CLI @elcrm/*: update, create, migrate front/components/css-sanitize, audit, doctor, test, build, css",
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>` |
@@ -2,7 +2,7 @@
2
2
 
3
3
  # `elcrm` CLI
4
4
 
5
- Глобально: `bun i -g elcrm` или `elcrm upd` (latest). В приложении — после смены `@elcrm/*`:
5
+ Глобально: `bun i -g elcrm`. В приложении — пакеты и CLI одной командой:
6
6
 
7
7
  ```bash
8
8
  elcrm update --fix --test
@@ -13,8 +13,7 @@ elcrm cursor
13
13
 
14
14
  | Команда | Зачем |
15
15
  | --- | --- |
16
- | `upd` | сам CLI latest (`bun i -g elcrm@latest`) |
17
- | `update [--dry] [--core] [--fix] [--test]` | latest `@elcrm/*` в проекте; `--fix` = migrate front + css; `--test` = elcrm test |
16
+ | `update [--dry] [--core] [--fix] [--test]` | `bun i -g elcrm@latest` + `@elcrm/*` в проекте; `--fix` = migrate front + css; `--test` = elcrm test |
18
17
  | `create` / `init` | шаблоны `app` \| `lib` \| `panel` |
19
18
  | `migrate <name> [--dry]` | `front` (пакет), `form-aliases`, `form-tokens`, `components`, `size-sml`, `field-border`, `modal-create`, `legacy-hacks`, `socket-server` |
20
19
  | `audit [--fix]` | старые API; `--fix` = migrate front |