@csszyx/cli 0.10.6 → 0.10.8

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/bin.mjs CHANGED
@@ -6,18 +6,18 @@ import path__default from 'node:path';
6
6
  import fs from 'fs-extra';
7
7
  import ora from 'ora';
8
8
  import pc from 'picocolors';
9
+ import { readFile } from 'node:fs/promises';
10
+ import { transformSourceCode, transform } from '@csszyx/compiler';
11
+ import fg from 'fast-glob';
9
12
  import { parseExpression } from '@babel/parser';
10
- import { transform } from '@csszyx/compiler';
11
- import { i as transformHtmlSourceSimple, t as transformSource, h as generateTypes } from './shared/cli.C2lx9IpE.mjs';
13
+ import { i as transformHtmlSourceSimple, t as transformSource, h as generateTypes } from './shared/cli.D8dbl_-u.mjs';
12
14
  import { execa } from 'execa';
13
15
  import prompts from 'prompts';
14
16
  import readline from 'node:readline';
15
- import fg from 'fast-glob';
16
17
  import { runNextPrebuild } from '@csszyx/unplugin/next-prebuild';
17
18
  import { NextSafelistWatcher } from '@csszyx/unplugin/next-watcher';
18
19
  import { watch } from 'chokidar';
19
20
  import { Minimatch } from 'minimatch';
20
- import 'node:fs/promises';
21
21
  import 'node:url';
22
22
  import 'tailwindcss/resolveConfig.js';
23
23
  import '@babel/types';
@@ -178,6 +178,81 @@ function formatBytes(bytes) {
178
178
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
179
179
  }
180
180
 
181
+ const DEFAULT_IGNORE$1 = [
182
+ "**/node_modules/**",
183
+ "**/dist/**",
184
+ "**/build/**",
185
+ "**/.next/**",
186
+ "**/.nuxt/**",
187
+ "**/.astro/**"
188
+ ];
189
+ async function check(options = {}) {
190
+ const cwd = options.cwd ?? process.cwd();
191
+ const patterns = options.pattern ? [options.pattern] : ["**/*.{jsx,tsx}"];
192
+ const ignore = [...DEFAULT_IGNORE$1, ...options.ignore ?? []];
193
+ process.env.CSSZYX_NO_PROJECT_SCAN_HINT = "1";
194
+ printHeader("csszyx check \u2014 static sz diagnostics");
195
+ const s = spinner.start("Scanning for files...");
196
+ let files;
197
+ try {
198
+ files = await fg(patterns, { cwd, ignore, absolute: true });
199
+ } catch (err) {
200
+ s.fail("File scan failed");
201
+ printWarn(`Could not scan files: ${err instanceof Error ? err.message : String(err)}`);
202
+ process.exitCode = 1;
203
+ return;
204
+ }
205
+ s.succeed(`Found ${files.length} files`);
206
+ const issues = [];
207
+ let currentFile = "";
208
+ const originalWarn = console.warn;
209
+ console.warn = (...args) => {
210
+ const message = args.map(String).join(" ");
211
+ if (message.startsWith("[csszyx]")) {
212
+ issues.push({ file: currentFile, message: message.replace(/^\[csszyx\]\s*/, "") });
213
+ }
214
+ };
215
+ try {
216
+ for (const file of files) {
217
+ let source;
218
+ try {
219
+ source = await readFile(file, "utf8");
220
+ } catch {
221
+ continue;
222
+ }
223
+ if (!source.includes("sz")) {
224
+ continue;
225
+ }
226
+ currentFile = path__default.relative(cwd, file);
227
+ try {
228
+ transformSourceCode(source, file, { rootDir: cwd });
229
+ } catch {
230
+ }
231
+ }
232
+ } finally {
233
+ console.warn = originalWarn;
234
+ }
235
+ if (issues.length === 0) {
236
+ printSuccess(`No sz issues found across ${files.length} files.`);
237
+ return;
238
+ }
239
+ const byFile = /* @__PURE__ */ new Map();
240
+ for (const { file, message } of issues) {
241
+ const list = byFile.get(file) ?? [];
242
+ list.push(message);
243
+ byFile.set(file, list);
244
+ }
245
+ for (const [file, messages] of byFile) {
246
+ printWarn(file);
247
+ for (const message of messages) {
248
+ printInfo(` ${message}`);
249
+ }
250
+ }
251
+ printWarn(`
252
+ \u2716 ${issues.length} sz issue(s) in ${byFile.size} file(s).`);
253
+ process.exitCode = 1;
254
+ }
255
+
181
256
  function detectFramework(cwd) {
182
257
  try {
183
258
  const pkgPath = path__default.join(cwd, "package.json");
@@ -1445,6 +1520,71 @@ function normalizeDebounceMs(debounceMs) {
1445
1520
  return parsed;
1446
1521
  }
1447
1522
 
1523
+ const DEFAULT_IGNORE = [
1524
+ "**/node_modules/**",
1525
+ "**/dist/**",
1526
+ "**/build/**",
1527
+ "**/.next/**",
1528
+ "**/.nuxt/**",
1529
+ "**/.astro/**"
1530
+ ];
1531
+ const CLASS_SELECTOR_RE = /\.(-?[a-z_][\w-]*)/gi;
1532
+ function stripNonSelectorText(css) {
1533
+ return css.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ").replace(/url\([^)]*\)/gi, " ").replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
1534
+ }
1535
+ function looksLikeToken(name) {
1536
+ return /^[a-z][a-z0-9]{0,2}$/i.test(name);
1537
+ }
1538
+ async function scanCollisions(options = {}) {
1539
+ const cwd = options.cwd ?? process.cwd();
1540
+ const patterns = options.pattern ? [options.pattern] : ["**/*.{css,scss,sass,less}"];
1541
+ const ignore = [...DEFAULT_IGNORE, ...options.ignore ?? []];
1542
+ printHeader("csszyx scan-collisions \u2014 mangle token risks");
1543
+ const s = spinner.start("Scanning stylesheets...");
1544
+ let files;
1545
+ try {
1546
+ files = await fg(patterns, { cwd, ignore, absolute: true });
1547
+ } catch (err) {
1548
+ s.fail("Stylesheet scan failed");
1549
+ printWarn(`Could not scan files: ${err instanceof Error ? err.message : String(err)}`);
1550
+ process.exitCode = 1;
1551
+ return;
1552
+ }
1553
+ s.succeed(`Scanned ${files.length} stylesheet(s)`);
1554
+ const risky = /* @__PURE__ */ new Map();
1555
+ for (const file of files) {
1556
+ let css;
1557
+ try {
1558
+ css = await readFile(file, "utf8");
1559
+ } catch {
1560
+ continue;
1561
+ }
1562
+ const rel = path__default.relative(cwd, file);
1563
+ for (const match of stripNonSelectorText(css).matchAll(CLASS_SELECTOR_RE)) {
1564
+ const name = match[1];
1565
+ if (looksLikeToken(name)) {
1566
+ (risky.get(name) ?? risky.set(name, /* @__PURE__ */ new Set()).get(name))?.add(rel);
1567
+ }
1568
+ }
1569
+ }
1570
+ if (risky.size === 0) {
1571
+ printSuccess("No collision-prone class names found \u2014 mangling is safe to enable.");
1572
+ return;
1573
+ }
1574
+ const names = [...risky.keys()].sort();
1575
+ printWarn(`${names.length} class name(s) could collide with a mangled token:`);
1576
+ for (const name of names) {
1577
+ const where = [...risky.get(name) ?? []].slice(0, 3).join(", ");
1578
+ printInfo(` .${name} (in ${where})`);
1579
+ }
1580
+ printInfo("\nPreferred: rename these in your own CSS to something specific");
1581
+ printInfo(" (e.g. `.x` \u2192 `.resize-handle-x`) \u2014 short names also clash on");
1582
+ printInfo(" specificity with other libraries.");
1583
+ printInfo("\nFor names in a third-party stylesheet you cannot edit, reserve them:");
1584
+ printInfo(` production: { mangle: true, mangleExclude: ${JSON.stringify(names)} }`);
1585
+ process.exitCode = 1;
1586
+ }
1587
+
1448
1588
  const cli = cac("csszyx");
1449
1589
  normalizeNextCommandAlias(process.argv);
1450
1590
  function readCliVersion() {
@@ -1500,6 +1640,23 @@ cli.command("doctor", "Diagnose mangling issues").option("--verbose", "Show deta
1500
1640
  cwd: options.cwd
1501
1641
  });
1502
1642
  });
1643
+ cli.command("check", "Scan the whole project for unknown/aliased sz keys (CI-friendly)").option("--pattern <glob>", "Glob of source files to scan").option("--ignore <glob>", "Extra ignore glob (repeatable)").option("--cwd <dir>", "Current working directory").action(async (options) => {
1644
+ await check({
1645
+ cwd: options.cwd,
1646
+ pattern: options.pattern,
1647
+ ignore: options.ignore ? Array.isArray(options.ignore) ? options.ignore : [options.ignore] : void 0
1648
+ });
1649
+ });
1650
+ cli.command(
1651
+ "scan-collisions",
1652
+ "Find class names that could collide with a mangled token (for production.mangleExclude)"
1653
+ ).option("--pattern <glob>", "Glob of stylesheet files to scan").option("--ignore <glob>", "Extra ignore glob (repeatable)").option("--cwd <dir>", "Current working directory").action(async (options) => {
1654
+ await scanCollisions({
1655
+ cwd: options.cwd,
1656
+ pattern: options.pattern,
1657
+ ignore: options.ignore ? Array.isArray(options.ignore) ? options.ignore : [options.ignore] : void 0
1658
+ });
1659
+ });
1503
1660
  cli.command("explain <sz>", "Print the Tailwind className an sz object compiles to").action(
1504
1661
  (sz) => {
1505
1662
  explain(sz);
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as classNameToSzObject, e as extractScreenKeys, a as extractSpacingKeys, f as findConfigFile, b as flattenColors, g as generateAndWriteTypes, d as generateTypeDeclarations, h as generateTypes, t as migrateSource, s as scanTailwindConfig, w as writeDeclarationFile } from './shared/cli.C2lx9IpE.mjs';
1
+ export { c as classNameToSzObject, e as extractScreenKeys, a as extractSpacingKeys, f as findConfigFile, b as flattenColors, g as generateAndWriteTypes, d as generateTypeDeclarations, h as generateTypes, t as migrateSource, s as scanTailwindConfig, w as writeDeclarationFile } from './shared/cli.D8dbl_-u.mjs';
2
2
  import 'node:path';
3
3
  import 'node:fs';
4
4
  import 'node:fs/promises';
@@ -869,113 +869,6 @@ async function generateTypes(options = {}) {
869
869
  }
870
870
  }
871
871
 
872
- function generateSzExpression(obj) {
873
- return `{${objectToString(obj)}}`;
874
- }
875
- function generateSzHtmlValue(obj, braces = false) {
876
- const s = objectToString(obj);
877
- if (braces) {
878
- return s;
879
- }
880
- if (s.startsWith("{ ") && s.endsWith(" }")) {
881
- return s.slice(2, -2);
882
- }
883
- if (s.startsWith("{") && s.endsWith("}")) {
884
- return s.slice(1, -1).trim();
885
- }
886
- return s;
887
- }
888
- function generateSzObjectLiteral(obj) {
889
- return objectToString(obj);
890
- }
891
- function objectToString(obj, indent = 0) {
892
- const entries = Object.entries(obj);
893
- if (entries.length === 0) {
894
- return "{}";
895
- }
896
- const spaces = " ".repeat(indent);
897
- const innerSpaces = " ".repeat(indent + 2);
898
- if (entries.length <= 2 && !hasDeepNesting(obj)) {
899
- const parts = entries.map(([k, v]) => `${formatKey(k)}: ${formatValue(v, indent)}`);
900
- return `{ ${parts.join(", ")} }`;
901
- }
902
- const lines = entries.map(
903
- ([k, v]) => `${innerSpaces}${formatKey(k)}: ${formatValue(v, indent + 2)},`
904
- );
905
- return `{
906
- ${lines.join("\n")}
907
- ${spaces}}`;
908
- }
909
- function hasDeepNesting(obj) {
910
- return Object.values(obj).some(
911
- (v) => typeof v === "object" && v !== null && !isColorOpacityObj(v) && !isGradientObj(v)
912
- );
913
- }
914
- function isColorOpacityObj(v) {
915
- return typeof v === "object" && v !== null && "color" in v && "op" in v;
916
- }
917
- function isGradientObj(v) {
918
- return typeof v === "object" && v !== null && "gradient" in v;
919
- }
920
- function formatKey(key) {
921
- if (/^[a-z_$][\w$]*$/i.test(key)) {
922
- return key;
923
- }
924
- return `'${key}'`;
925
- }
926
- function formatValue(value, indent) {
927
- if (value === true) {
928
- return "true";
929
- }
930
- if (value === false) {
931
- return "false";
932
- }
933
- if (value === null) {
934
- return "null";
935
- }
936
- if (typeof value === "number") {
937
- return String(value);
938
- }
939
- if (typeof value === "string") {
940
- return `'${escapeString(value)}'`;
941
- }
942
- if (Array.isArray(value)) {
943
- const items = value.map((v) => formatValue(v, indent));
944
- return `[${items.join(", ")}]`;
945
- }
946
- if (typeof value === "object") {
947
- if (isColorOpacityObj(value)) {
948
- const parts = [`color: '${escapeString(String(value.color))}'`];
949
- if (typeof value.op === "number") {
950
- parts.push(`op: ${value.op}`);
951
- } else {
952
- parts.push(`op: '${escapeString(String(value.op))}'`);
953
- }
954
- return `{ ${parts.join(", ")} }`;
955
- }
956
- if (isGradientObj(value)) {
957
- const grad = value;
958
- const parts = [`gradient: '${grad.gradient}'`];
959
- if ("dir" in grad) {
960
- if (typeof grad.dir === "number") {
961
- parts.push(`dir: ${grad.dir}`);
962
- } else {
963
- parts.push(`dir: '${escapeString(String(grad.dir))}'`);
964
- }
965
- }
966
- if ("in" in grad) {
967
- parts.push(`in: '${escapeString(String(grad.in))}'`);
968
- }
969
- return `{ ${parts.join(", ")} }`;
970
- }
971
- return objectToString(value, indent);
972
- }
973
- return String(value);
974
- }
975
- function escapeString(s) {
976
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
977
- }
978
-
979
872
  const REVERSE_PROPERTY_MAP = {
980
873
  // Background (ambiguous — disambiguated in class-parser)
981
874
  bg: "bg",
@@ -2474,6 +2367,113 @@ function parseStringValue(value) {
2474
2367
  return value;
2475
2368
  }
2476
2369
 
2370
+ function generateSzExpression(obj) {
2371
+ return `{${objectToString(obj)}}`;
2372
+ }
2373
+ function generateSzHtmlValue(obj, braces = false) {
2374
+ const s = objectToString(obj);
2375
+ if (braces) {
2376
+ return s;
2377
+ }
2378
+ if (s.startsWith("{ ") && s.endsWith(" }")) {
2379
+ return s.slice(2, -2);
2380
+ }
2381
+ if (s.startsWith("{") && s.endsWith("}")) {
2382
+ return s.slice(1, -1).trim();
2383
+ }
2384
+ return s;
2385
+ }
2386
+ function generateSzObjectLiteral(obj) {
2387
+ return objectToString(obj);
2388
+ }
2389
+ function objectToString(obj, indent = 0) {
2390
+ const entries = Object.entries(obj);
2391
+ if (entries.length === 0) {
2392
+ return "{}";
2393
+ }
2394
+ const spaces = " ".repeat(indent);
2395
+ const innerSpaces = " ".repeat(indent + 2);
2396
+ if (entries.length <= 2 && !hasDeepNesting(obj)) {
2397
+ const parts = entries.map(([k, v]) => `${formatKey(k)}: ${formatValue(v, indent)}`);
2398
+ return `{ ${parts.join(", ")} }`;
2399
+ }
2400
+ const lines = entries.map(
2401
+ ([k, v]) => `${innerSpaces}${formatKey(k)}: ${formatValue(v, indent + 2)},`
2402
+ );
2403
+ return `{
2404
+ ${lines.join("\n")}
2405
+ ${spaces}}`;
2406
+ }
2407
+ function hasDeepNesting(obj) {
2408
+ return Object.values(obj).some(
2409
+ (v) => typeof v === "object" && v !== null && !isColorOpacityObj(v) && !isGradientObj(v)
2410
+ );
2411
+ }
2412
+ function isColorOpacityObj(v) {
2413
+ return typeof v === "object" && v !== null && "color" in v && "op" in v;
2414
+ }
2415
+ function isGradientObj(v) {
2416
+ return typeof v === "object" && v !== null && "gradient" in v;
2417
+ }
2418
+ function formatKey(key) {
2419
+ if (/^[a-z_$][\w$]*$/i.test(key)) {
2420
+ return key;
2421
+ }
2422
+ return `'${key}'`;
2423
+ }
2424
+ function formatValue(value, indent) {
2425
+ if (value === true) {
2426
+ return "true";
2427
+ }
2428
+ if (value === false) {
2429
+ return "false";
2430
+ }
2431
+ if (value === null) {
2432
+ return "null";
2433
+ }
2434
+ if (typeof value === "number") {
2435
+ return String(value);
2436
+ }
2437
+ if (typeof value === "string") {
2438
+ return `'${escapeString(value)}'`;
2439
+ }
2440
+ if (Array.isArray(value)) {
2441
+ const items = value.map((v) => formatValue(v, indent));
2442
+ return `[${items.join(", ")}]`;
2443
+ }
2444
+ if (typeof value === "object") {
2445
+ if (isColorOpacityObj(value)) {
2446
+ const parts = [`color: '${escapeString(String(value.color))}'`];
2447
+ if (typeof value.op === "number") {
2448
+ parts.push(`op: ${value.op}`);
2449
+ } else {
2450
+ parts.push(`op: '${escapeString(String(value.op))}'`);
2451
+ }
2452
+ return `{ ${parts.join(", ")} }`;
2453
+ }
2454
+ if (isGradientObj(value)) {
2455
+ const grad = value;
2456
+ const parts = [`gradient: '${grad.gradient}'`];
2457
+ if ("dir" in grad) {
2458
+ if (typeof grad.dir === "number") {
2459
+ parts.push(`dir: ${grad.dir}`);
2460
+ } else {
2461
+ parts.push(`dir: '${escapeString(String(grad.dir))}'`);
2462
+ }
2463
+ }
2464
+ if ("in" in grad) {
2465
+ parts.push(`in: '${escapeString(String(grad.in))}'`);
2466
+ }
2467
+ return `{ ${parts.join(", ")} }`;
2468
+ }
2469
+ return objectToString(value, indent);
2470
+ }
2471
+ return String(value);
2472
+ }
2473
+ function escapeString(s) {
2474
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2475
+ }
2476
+
2477
2477
  function tokenize(className) {
2478
2478
  return className.trim().split(/\s+/).filter(Boolean);
2479
2479
  }
@@ -3244,6 +3244,24 @@ function normalizeSzObject(obj, replacements) {
3244
3244
  count++;
3245
3245
  continue;
3246
3246
  }
3247
+ if (keyName === "font" && key.start != null && key.end != null) {
3248
+ let fontValue = null;
3249
+ if (t.isStringLiteral(prop.value)) {
3250
+ fontValue = prop.value.value;
3251
+ } else if (t.isNumericLiteral(prop.value)) {
3252
+ fontValue = String(prop.value.value);
3253
+ }
3254
+ const resolved = fontValue !== null ? disambiguateFont(fontValue)?.prop : void 0;
3255
+ if (resolved && resolved !== "font") {
3256
+ replacements.push({
3257
+ start: key.start,
3258
+ end: key.end,
3259
+ text: t.isStringLiteral(key) ? `'${resolved}'` : resolved
3260
+ });
3261
+ count++;
3262
+ continue;
3263
+ }
3264
+ }
3247
3265
  const suggestion = SUGGESTION_MAP[keyName];
3248
3266
  if (suggestion && isCleanCanonicalTarget(suggestion) && suggestion !== keyName && key.start != null && key.end != null) {
3249
3267
  replacements.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csszyx/cli",
3
- "version": "0.10.6",
3
+ "version": "0.10.8",
4
4
  "description": "Command-line tools for csszyx",
5
5
  "keywords": [
6
6
  "csszyx",
@@ -49,9 +49,9 @@
49
49
  "picocolors": "^1.0.0",
50
50
  "prompts": "^2.4.2",
51
51
  "tailwindcss": "^3.4.1",
52
- "@csszyx/compiler": "0.10.6",
53
- "@csszyx/unplugin": "0.10.6",
54
- "@csszyx/types": "0.10.6"
52
+ "@csszyx/compiler": "0.10.8",
53
+ "@csszyx/types": "0.10.8",
54
+ "@csszyx/unplugin": "0.10.8"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/fs-extra": "^11.0.4",