pasika 0.10.117 → 0.10.119

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.
@@ -128,6 +128,7 @@ declare const tailwindRules: {
128
128
  "stylesheet-ordering": _eslint_css.CSSRuleDefinition;
129
129
  "custom-utility-apply": _eslint_css.CSSRuleDefinition;
130
130
  "utility-mapping": _eslint_css.CSSRuleDefinition;
131
+ "utility-name-parity": _eslint_css.CSSRuleDefinition;
131
132
  "css-entry-point": _eslint_css.CSSRuleDefinition;
132
133
  "feature-css-placement": _eslint_css.CSSRuleDefinition;
133
134
  "global-stylesheet": _eslint_css.CSSRuleDefinition;
@@ -168,6 +169,7 @@ declare const pasikaPlugin: {
168
169
  "stylesheet-ordering": _eslint_css.CSSRuleDefinition;
169
170
  "custom-utility-apply": _eslint_css.CSSRuleDefinition;
170
171
  "utility-mapping": _eslint_css.CSSRuleDefinition;
172
+ "utility-name-parity": _eslint_css.CSSRuleDefinition;
171
173
  "css-entry-point": _eslint_css.CSSRuleDefinition;
172
174
  "feature-css-placement": _eslint_css.CSSRuleDefinition;
173
175
  "global-stylesheet": _eslint_css.CSSRuleDefinition;
@@ -10095,6 +10095,7 @@ function themeClasses(name) {
10095
10095
  }
10096
10096
  return ["bg", "text", "border", "fill", "stroke", "ring"].map((prefix) => `${prefix}-${stem}`);
10097
10097
  }
10098
+ if (name.startsWith("--background-image-")) return [`bg-${name.slice("--background-image-".length)}`];
10098
10099
  if (name.startsWith("--spacing-")) {
10099
10100
  const stem = name.slice("--spacing-".length);
10100
10101
  return [
@@ -11160,6 +11161,7 @@ var themeOperations = [
11160
11161
  { property: "tab-size", prefix: "tab", namespace: "tab-size" },
11161
11162
  { property: "border-radius", prefix: "rounded", namespace: "radius" },
11162
11163
  { property: "background-color", prefix: "bg", namespace: "color" },
11164
+ { property: "background-image", prefix: "bg", namespace: "background-image" },
11163
11165
  { property: "color", prefix: "text", namespace: "color" },
11164
11166
  { property: "border-color", prefix: "border", namespace: "color" },
11165
11167
  { property: "outline-color", prefix: "outline", namespace: "color" },
@@ -11194,9 +11196,9 @@ function themeCandidate(name, utilityText) {
11194
11196
  const typed = /^@utility\s+[\w-]+\s*\{\s*@apply\s+(?<kind>[a-z]+)-\((?<hint>[a-z-]+:)?(?<variable>--[a-z][a-z0-9-]*)\);?\s*\}$/u.exec(
11195
11197
  source
11196
11198
  );
11197
- const property = arbitrary?.groups?.property;
11199
+ const direct = /^@utility\s+[\w-]+\s*\{\s*(?<property>[a-z-]+)\s*:\s*(?<value>[^;{}\r\n]+);?\s*\}$/u.exec(source);
11200
+ const property = arbitrary?.groups?.property ?? direct?.groups?.property;
11198
11201
  const matched = themeOperations.find((operation) => {
11199
- if (!name.startsWith(`${operation.prefix}-`) || name.length <= operation.prefix.length + 1) return false;
11200
11202
  if (property) return property === operation.property;
11201
11203
  if (typed?.groups?.kind !== operation.prefix) return false;
11202
11204
  const hint = typed.groups.hint;
@@ -11204,23 +11206,23 @@ function themeCandidate(name, utilityText) {
11204
11206
  if (operation.prefix === "font") {
11205
11207
  return hint === (operation.property === "font-family" ? "family-name:" : "number:");
11206
11208
  }
11209
+ if (operation.prefix === "bg") {
11210
+ return hint === (operation.property === "background-image" ? "image:" : "color:");
11211
+ }
11212
+ if (["border", "outline", "decoration", "stroke"].includes(operation.prefix)) return hint === "color:";
11207
11213
  return hint === void 0;
11208
11214
  });
11209
11215
  if (!matched) return void 0;
11210
- const value = arbitrary?.groups?.value?.trim() ?? (typed?.groups?.variable ? `var(${typed.groups.variable})` : void 0);
11216
+ const value = arbitrary?.groups?.value?.trim() ?? direct?.groups?.value?.trim() ?? (typed?.groups?.variable ? `var(${typed.groups.variable})` : void 0);
11211
11217
  if (!value) return void 0;
11212
- const simple = /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:px|rem|em|%|vh|vw|ch|deg)?$/u.test(value);
11213
- const variable = /^var\(--[a-z][a-z0-9-]*\)$/u.test(value);
11214
- const color = /^#[\da-f]{3,8}$/iu.test(value);
11215
- const keyword = /^[a-z][a-z-]*$/u.test(value);
11216
- const ratio = /^\d+\/\d+$/u.test(value);
11217
- if (!simple && !variable && !color && !keyword && !ratio) return void 0;
11218
- if (matched.property === "font-size" && value.startsWith("-")) return void 0;
11219
- const stem = name.slice(matched.prefix.length + 1);
11218
+ if (arbitrary && (/\\|["']|\burl\(/iu.test(value) || /var\(--[^)]*_/iu.test(value))) return void 0;
11219
+ const cssValue = arbitrary ? value.replaceAll("_", " ") : value;
11220
+ if (matched.property === "font-size" && cssValue.startsWith("-")) return void 0;
11221
+ const stem = name.startsWith(`${matched.prefix}-`) ? name.slice(matched.prefix.length + 1) : void 0;
11220
11222
  return {
11221
- token: `--${matched.namespace}-${stem}`,
11223
+ token: stem ? `--${matched.namespace}-${stem}` : "",
11222
11224
  property: matched.property,
11223
- value,
11225
+ value: cssValue,
11224
11226
  prefix: matched.prefix,
11225
11227
  namespace: matched.namespace
11226
11228
  };
@@ -11282,6 +11284,37 @@ var utilityMappingRule = {
11282
11284
  includeReferences: false
11283
11285
  });
11284
11286
  const projectTokens = graph.definitions.filter((symbol) => symbol.kind === "theme-token");
11287
+ const rootDefinitions = /* @__PURE__ */ new Map();
11288
+ for (const symbol of graph.definitions) {
11289
+ if (symbol.kind !== "root-variable" || !symbol.value) continue;
11290
+ const values = rootDefinitions.get(symbol.name) ?? [];
11291
+ values.push({ value: symbol.value, scope: symbol.scope ?? "default" });
11292
+ rootDefinitions.set(symbol.name, values);
11293
+ }
11294
+ function comparableValue(value, visited = /* @__PURE__ */ new Set()) {
11295
+ const text = value.trim();
11296
+ const match = /^var\((?<name>--[a-z][a-z0-9-]*)\)$/u.exec(text);
11297
+ const name = match?.groups?.name;
11298
+ if (!name) {
11299
+ const hex = /^#(?<digits>[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/iu.exec(text)?.groups?.digits;
11300
+ if (!hex) return text;
11301
+ const lower = hex.toLowerCase();
11302
+ return lower.length <= 4 ? Array.from(lower, (digit) => digit + digit).join("") : lower;
11303
+ }
11304
+ if (visited.has(name)) return void 0;
11305
+ const values = rootDefinitions.get(name);
11306
+ if (!values) return text;
11307
+ if (values.length !== 1 || values[0]?.scope !== "default") return void 0;
11308
+ const next = values[0].value;
11309
+ if (!next) return void 0;
11310
+ visited.add(name);
11311
+ return comparableValue(next, visited);
11312
+ }
11313
+ function equivalentValue(a, b) {
11314
+ const left = comparableValue(a);
11315
+ const right = comparableValue(b);
11316
+ return left !== void 0 && right !== void 0 && left === right;
11317
+ }
11285
11318
  return {
11286
11319
  "StyleSheet:exit"(node) {
11287
11320
  const utilities = atrulesNamed(node, "utility");
@@ -11307,6 +11340,16 @@ var utilityMappingRule = {
11307
11340
  themeDefinitions.set(child.property, definitions);
11308
11341
  }
11309
11342
  }
11343
+ for (const cssRule of node.children) {
11344
+ if (cssRule.type !== "Rule") continue;
11345
+ if (context.sourceCode.getText(cssRule.prelude).trim() !== ":root") continue;
11346
+ for (const child of blockChildren2(cssRule)) {
11347
+ if (child.type !== "Declaration" || !child.property.startsWith("--")) continue;
11348
+ if (rootDefinitions.has(child.property)) continue;
11349
+ const value = declarationValue(context.sourceCode.getText(child));
11350
+ if (value) rootDefinitions.set(child.property, [{ value, scope: "default" }]);
11351
+ }
11352
+ }
11310
11353
  const rootValues = /* @__PURE__ */ new Map();
11311
11354
  const overriddenVariables = /* @__PURE__ */ new Set();
11312
11355
  for (const cssRule of rules(node)) {
@@ -11358,8 +11401,55 @@ var utilityMappingRule = {
11358
11401
  break;
11359
11402
  }
11360
11403
  }
11404
+ function matchingThemeToken(candidate) {
11405
+ const namespacePrefix = `--${candidate.namespace}-`;
11406
+ const available = [
11407
+ ...projectTokens,
11408
+ ...[...themeDefinitions].flatMap(
11409
+ ([name, definitions]) => definitions.length === 1 && definitions[0] ? [{ name, value: definitions[0].value }] : []
11410
+ )
11411
+ ];
11412
+ for (const token of available) {
11413
+ if (!token.value || !token.name.startsWith(namespacePrefix) || token.name.includes("--", 2)) continue;
11414
+ if (!equivalentValue(candidate.value, token.value)) continue;
11415
+ if (candidate.namespace === "text" && ["--line-height", "--letter-spacing", "--font-weight"].some(
11416
+ (suffix) => themeDefinitions.has(`${token.name}${suffix}`) || projectTokens.some((entry) => entry.name === `${token.name}${suffix}`)
11417
+ )) {
11418
+ continue;
11419
+ }
11420
+ const generatedClass = `${candidate.prefix}-${token.name.slice(namespacePrefix.length)}`;
11421
+ if (candidate.namespace === "color" && !themeClasses(token.name).includes(generatedClass)) continue;
11422
+ return generatedClass;
11423
+ }
11424
+ return void 0;
11425
+ }
11361
11426
  for (const utility of utilities) {
11362
11427
  const name = preludeIdentifiers(utility)[0] ?? "unknown";
11428
+ const source = context.sourceCode.getText(utility);
11429
+ const zIndex = /^@utility\s+[\w-]+\s*\{\s*@apply\s+\[z-index:(?<value>-?\d+|auto)\];?\s*\}$/u.exec(source)?.groups?.value;
11430
+ const numericZIndex = zIndex && zIndex !== "auto" ? Number(zIndex) : void 0;
11431
+ let nativeClass;
11432
+ if (zIndex === "auto") {
11433
+ nativeClass = "z-auto";
11434
+ } else if (numericZIndex !== void 0 && Number.isSafeInteger(numericZIndex)) {
11435
+ nativeClass = numericZIndex < 0 ? `-z-${String(-numericZIndex)}` : `z-${String(numericZIndex)}`;
11436
+ }
11437
+ if (nativeClass && zIndex) {
11438
+ context.report({
11439
+ node: utility,
11440
+ message: `Custom utility "${name}" duplicates built-in "${nativeClass}" (z-index: ${zIndex}); use "${nativeClass}" directly.`
11441
+ });
11442
+ continue;
11443
+ }
11444
+ const candidate = themeCandidate(name, source);
11445
+ const equivalent = candidate ? matchingThemeToken(candidate) : void 0;
11446
+ if (candidate && equivalent && nameCounts.get(name) === 1) {
11447
+ context.report({
11448
+ node: utility,
11449
+ message: `Custom utility "${name}" duplicates theme-generated "${equivalent}" (${candidate.property}: ${candidate.value}); reuse "${equivalent}" instead.`
11450
+ });
11451
+ continue;
11452
+ }
11363
11453
  const operationCount = applyOperationCount(utility);
11364
11454
  if (operationCount !== 1) {
11365
11455
  context.report({
@@ -11368,46 +11458,244 @@ var utilityMappingRule = {
11368
11458
  });
11369
11459
  continue;
11370
11460
  }
11371
- if (nameCounts.get(name) !== 1) continue;
11372
- const candidate = themeCandidate(name, context.sourceCode.getText(utility));
11373
- if (!candidate) continue;
11374
- const tokenAlreadyDefined = themeTokens.has(candidate.token) || projectTokens.some((token) => token.name === candidate.token);
11375
- const namespacePrefix = `--${candidate.namespace}-`;
11376
- const staticValue = !/\b(?:var|env|attr|calc|min|max|clamp|color-mix|light-dark)\s*\(/iu.test(
11377
- candidate.value
11378
- );
11379
- const valueKey = (value) => {
11380
- const input = value.trim();
11381
- const digits = /^#(?<hex>[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/iu.exec(input)?.groups?.hex;
11382
- if (digits) {
11383
- const lower = digits.toLowerCase();
11384
- return lower.length <= 4 ? Array.from(lower, (digit) => digit + digit).join("") : lower;
11385
- }
11386
- return input;
11387
- };
11388
- const sameOperation = (token, value) => token.startsWith(namespacePrefix) && !token.includes("--", 2) && valueKey(value) === valueKey(candidate.value) && (candidate.namespace !== "text" || !["--line-height", "--letter-spacing", "--font-weight"].some(
11389
- (suffix) => themeDefinitions.has(`${token}${suffix}`) || projectTokens.some((definition) => definition.name === `${token}${suffix}`)
11390
- ));
11391
- const equivalent = staticValue ? projectTokens.find((token) => token.value !== void 0 && sameOperation(token.name, token.value)) : void 0;
11392
- const localEquivalent = staticValue ? [...themeDefinitions].find(
11393
- ([token, definitions]) => definitions.length === 1 && definitions[0]?.value !== void 0 && sameOperation(token, definitions[0].value)
11394
- )?.[0] : void 0;
11395
- const existingToken = equivalent?.name ?? localEquivalent;
11396
- if (tokenAlreadyDefined && !existingToken) continue;
11397
- if (existingToken) {
11398
- const className = `${candidate.prefix}-${existingToken.slice(namespacePrefix.length)}`;
11461
+ if (nameCounts.get(name) !== 1 || !candidate) continue;
11462
+ if (!candidate.token) {
11399
11463
  context.report({
11400
11464
  node: utility,
11401
- message: `Custom utility "${name}" duplicates theme-generated "${className}" (${candidate.property}: ${candidate.value}); reuse "${className}" instead.`
11465
+ message: `Custom utility "${name}" only maps ${candidate.property} (${candidate.value}); define an @theme token in the "--${candidate.namespace}-*" namespace and use its generated "${candidate.prefix}-*" class instead.`
11402
11466
  });
11403
11467
  continue;
11404
11468
  }
11469
+ const tokenAlreadyDefined = themeTokens.has(candidate.token) || projectTokens.some((token) => token.name === candidate.token);
11470
+ if (tokenAlreadyDefined) continue;
11405
11471
  if (candidate.property === "font-size" && themeTokens.has(`--color-${name.slice(5)}`)) continue;
11406
11472
  context.report({
11407
11473
  node: utility,
11408
11474
  message: `Custom utility "${name}" only maps ${candidate.property} (${candidate.value}); prefer @theme inline token "${candidate.token}" backed by a :root variable. Preserve the class name and compare compiled CSS before removing the utility.`
11409
11475
  });
11410
11476
  }
11477
+ function checkClass(cssNode) {
11478
+ if (cssNode.type === "Atrule") {
11479
+ if (cssNode.name !== "layer" || !("prelude" in cssNode) || !cssNode.prelude) return;
11480
+ if (context.sourceCode.getText(cssNode.prelude).trim() === "base") return;
11481
+ for (const child of blockChildren2(cssNode)) checkClass(child);
11482
+ return;
11483
+ }
11484
+ if (cssNode.type !== "Rule") return;
11485
+ const selector = context.sourceCode.getText(cssNode.prelude).trim();
11486
+ if (!/^\.[a-zA-Z_][\w-]*$/u.test(selector)) return;
11487
+ const children = blockChildren2(cssNode);
11488
+ if (children.length !== 1) return;
11489
+ const only = children[0];
11490
+ if (!only) return;
11491
+ const declarationText = context.sourceCode.getText(only).trim();
11492
+ if (only.type === "Atrule" && only.name === "apply") {
11493
+ const alias = /^@apply\s+(?<name>[a-z][a-z0-9-]*)\s*;?$/u.exec(declarationText)?.groups?.name;
11494
+ if (alias && [...projectTokens.map((token) => token.name), ...themeDefinitions.keys()].some(
11495
+ (name) => themeClasses(name).includes(alias)
11496
+ )) {
11497
+ context.report({
11498
+ node: cssNode,
11499
+ message: `CSS class "${selector}" only aliases theme-generated "${alias}"; use "${alias}" instead.`
11500
+ });
11501
+ return;
11502
+ }
11503
+ }
11504
+ const found = only.type === "Declaration" ? /^(?<property>[a-z-]+)\s*:\s*(?<value>[^;{}]+);?$/u.exec(declarationText) : /^@apply\s+\[(?<property>[a-z-]+):(?<value>[^\]\r\n;{}]+)\]\s*;?$/u.exec(declarationText);
11505
+ const property = found?.groups?.property;
11506
+ const value = found?.groups?.value?.trim();
11507
+ if (!property || !value || value.endsWith("!important")) return;
11508
+ const operation = themeOperations.find((item) => item.property === property);
11509
+ if (!operation) return;
11510
+ const candidate = {
11511
+ token: "",
11512
+ property,
11513
+ value,
11514
+ prefix: operation.prefix,
11515
+ namespace: operation.namespace
11516
+ };
11517
+ const equivalent = matchingThemeToken(candidate);
11518
+ if (!equivalent) return;
11519
+ context.report({
11520
+ node: cssNode,
11521
+ message: `CSS class "${selector}" duplicates theme-generated "${equivalent}" (${property}: ${value}); use "${equivalent}" instead.`
11522
+ });
11523
+ }
11524
+ for (const cssRule of node.children) checkClass(cssRule);
11525
+ }
11526
+ };
11527
+ }
11528
+ };
11529
+
11530
+ // eslint/rules/tailwind/utility-name-parity.ts
11531
+ import { spawnSync } from "child_process";
11532
+ import { createHash } from "crypto";
11533
+ import { existsSync as existsSync12 } from "fs";
11534
+ import path78 from "path";
11535
+ import { generate as generate4, parse as parse7, toPlainObject as toPlainObject3 } from "@eslint/css-tree";
11536
+ function numericPart(value) {
11537
+ const match = /^(?<negative>-?)(?<number>(?:\d+(?:\.\d+)?|\.\d+))(?<unit>%|px|rem|em|vw|vh|dvw|dvh|ch|fr|s|ms|deg|turn)?$/u.exec(
11538
+ value
11539
+ );
11540
+ if (!match?.groups?.number) return void 0;
11541
+ const number = String(Number(match.groups.number)).replace(".", "p");
11542
+ const sign = match.groups.negative === "-" ? "neg-" : "";
11543
+ return sign + number + (match.groups.unit === "%" ? "pct" : match.groups.unit ?? "");
11544
+ }
11545
+ function readableValue(value) {
11546
+ const words = value.trim().replace(/,/gu, "_comma_").split(/[_\s]+/u).filter(Boolean);
11547
+ if (words.length === 0 || words.length > 8) return void 0;
11548
+ const parts = [];
11549
+ for (const word of words) {
11550
+ const numeric = numericPart(word);
11551
+ if (numeric) {
11552
+ parts.push(numeric);
11553
+ continue;
11554
+ }
11555
+ const hex = /^#(?<color>[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/iu.exec(word)?.groups?.color;
11556
+ if (hex) {
11557
+ parts.push("hex", hex.toLowerCase());
11558
+ continue;
11559
+ }
11560
+ const variable = /^var\(--(?<name>[a-z][a-z0-9-]*)\)$/u.exec(word)?.groups?.name;
11561
+ if (variable) {
11562
+ parts.push("var", variable);
11563
+ continue;
11564
+ }
11565
+ if (/^[a-z][a-z0-9-]*$/iu.test(word)) {
11566
+ parts.push(word.toLowerCase());
11567
+ continue;
11568
+ }
11569
+ return void 0;
11570
+ }
11571
+ const slug = parts.join("-");
11572
+ return slug.length <= 70 ? slug : void 0;
11573
+ }
11574
+ function compoundValue(value) {
11575
+ const readable = readableValue(value);
11576
+ if (readable) return readable;
11577
+ const candidate = /^(?<fn>[a-z][a-z0-9-]*)\(/iu.exec(value.trim())?.groups?.fn?.toLowerCase();
11578
+ const hint = candidate?.endsWith("-") ? void 0 : candidate;
11579
+ const digest = createHash("sha256").update(value.trim()).digest("hex").slice(0, 12);
11580
+ return [hint, "hash", digest].filter(Boolean).join("-");
11581
+ }
11582
+ var TAILWIND_WORKER = String.raw`
11583
+ import { readFileSync } from "node:fs";
11584
+ import { compile } from "tailwindcss";
11585
+ const applies = JSON.parse(readFileSync(0, "utf8"));
11586
+ const results = [];
11587
+ for (const [index, apply] of applies.entries()) {
11588
+ const name = "pasika-probe-" + index;
11589
+ try {
11590
+ const compiler = await compile("@tailwind utilities; @utility " + name + " { " + apply + " }");
11591
+ results.push({ css: compiler.build([name]) });
11592
+ } catch (error) {
11593
+ results.push({ error: String(error instanceof Error ? error.message : error) });
11594
+ }
11595
+ }
11596
+ process.stdout.write(JSON.stringify(results));
11597
+ `;
11598
+ var compilerCache = /* @__PURE__ */ new Map();
11599
+ function isCompiledResult(value) {
11600
+ if (typeof value !== "object" || value === null) return false;
11601
+ return "css" in value && typeof value.css === "string" || "error" in value && typeof value.error === "string";
11602
+ }
11603
+ function compileApplies(applies, cwd) {
11604
+ const key = `${cwd}\0${JSON.stringify(applies)}`;
11605
+ const cached = compilerCache.get(key);
11606
+ if (cached) return cached;
11607
+ const child = spawnSync(process.execPath, ["--input-type=module", "--eval", TAILWIND_WORKER], {
11608
+ cwd,
11609
+ input: JSON.stringify(applies),
11610
+ encoding: "utf8",
11611
+ timeout: 3e4,
11612
+ maxBuffer: 8 * 1024 * 1024,
11613
+ windowsHide: true
11614
+ });
11615
+ let results;
11616
+ if (child.error || child.status !== 0) {
11617
+ const reason = child.error?.message ?? (child.stderr.trim() || "Tailwind compilation failed");
11618
+ results = applies.map(() => ({ error: reason }));
11619
+ } else {
11620
+ try {
11621
+ const parsed = JSON.parse(child.stdout);
11622
+ if (!Array.isArray(parsed)) throw new Error("Unexpected Tailwind output");
11623
+ const values = parsed;
11624
+ results = values.filter(isCompiledResult);
11625
+ if (results.length !== applies.length) throw new Error("Unexpected Tailwind output");
11626
+ } catch {
11627
+ results = applies.map(() => ({ error: "Invalid Tailwind compiler response" }));
11628
+ }
11629
+ }
11630
+ compilerCache.set(key, results);
11631
+ return results;
11632
+ }
11633
+ function compiledOperation(compiled, index) {
11634
+ if (!compiled.css) return void 0;
11635
+ const sheet = toPlainObject3(parse7(compiled.css, { parseValue: false }));
11636
+ const probe = rules(sheet).find((candidate) => selectorNames(candidate).includes(`pasika-probe-${String(index)}`));
11637
+ if (!probe) return void 0;
11638
+ const declarations = blockChildren2(probe).filter(
11639
+ (child) => child.type === "Declaration" && !child.property.startsWith("--tw-")
11640
+ );
11641
+ if (declarations.length !== 1) return void 0;
11642
+ const declaration = declarations[0];
11643
+ if (declaration?.type !== "Declaration") return void 0;
11644
+ return { property: declaration.property, value: generate4(declaration.value).trim() };
11645
+ }
11646
+ function expectedName(operation) {
11647
+ const value = compoundValue(operation.value);
11648
+ const property = operation.property.replace(/^-+/u, "").replace(/^background-/u, "bg-");
11649
+ return `${property}-${value}`;
11650
+ }
11651
+ function singleApply(utility, textOf) {
11652
+ const children = blockChildren2(utility);
11653
+ if (children.length !== 1) return void 0;
11654
+ const child = children[0];
11655
+ if (child?.type !== "Atrule" || child.name !== "apply") return void 0;
11656
+ return textOf(child);
11657
+ }
11658
+ var utilityNameParityRule = {
11659
+ meta: {
11660
+ schema: [],
11661
+ type: "problem",
11662
+ docs: {
11663
+ description: "Require single-operation custom utilities to have deterministic property/value-derived names."
11664
+ }
11665
+ },
11666
+ create(context) {
11667
+ return {
11668
+ "StyleSheet:exit"(node) {
11669
+ const candidates = atrulesNamed(node, "utility").flatMap((utility) => {
11670
+ const name = preludeIdentifiers(utility)[0];
11671
+ if (!name || name.endsWith("-*")) return [];
11672
+ const apply = singleApply(utility, (child) => context.sourceCode.getText(child));
11673
+ return apply ? [{ utility, name, apply }] : [];
11674
+ });
11675
+ if (candidates.length === 0) return;
11676
+ const physicalDir = path78.dirname(context.physicalFilename);
11677
+ const cwd = path78.isAbsolute(physicalDir) && existsSync12(physicalDir) ? physicalDir : context.cwd;
11678
+ const compiled = compileApplies(
11679
+ candidates.map((candidate) => candidate.apply),
11680
+ cwd
11681
+ );
11682
+ for (const [index, candidate] of candidates.entries()) {
11683
+ const result = compiled[index];
11684
+ const operation = result ? compiledOperation(result, index) : void 0;
11685
+ if (!operation) {
11686
+ context.report({
11687
+ node: candidate.utility,
11688
+ message: `Custom utility "${candidate.name}" applies "${candidate.apply}", but Tailwind cannot resolve exactly one CSS property${result?.error ? `: ${result.error}` : ""}; use an explicit [property:value] @apply mapping instead.`
11689
+ });
11690
+ continue;
11691
+ }
11692
+ const expected = expectedName(operation);
11693
+ if (candidate.name === expected) continue;
11694
+ context.report({
11695
+ node: candidate.utility,
11696
+ message: `Custom utility "${candidate.name}" maps ${operation.property}: ${operation.value}; rename it "${expected}" to preserve property/value parity.`
11697
+ });
11698
+ }
11411
11699
  }
11412
11700
  };
11413
11701
  }
@@ -11423,6 +11711,7 @@ var tailwindRules = {
11423
11711
  "stylesheet-ordering": stylesheetOrderingRule,
11424
11712
  "custom-utility-apply": customUtilityApplyRule,
11425
11713
  "utility-mapping": utilityMappingRule,
11714
+ "utility-name-parity": utilityNameParityRule,
11426
11715
  "css-entry-point": cssEntryPointRule,
11427
11716
  "feature-css-placement": featureCssPlacementRule,
11428
11717
  "global-stylesheet": globalStylesheetRule,
@@ -11434,7 +11723,7 @@ var tailwindRules = {
11434
11723
  };
11435
11724
 
11436
11725
  // eslint/rules/type-extraction.ts
11437
- import path78 from "path";
11726
+ import path79 from "path";
11438
11727
  var typeExtractionRule = {
11439
11728
  meta: {
11440
11729
  schema: [],
@@ -11445,7 +11734,7 @@ var typeExtractionRule = {
11445
11734
  },
11446
11735
  create(context) {
11447
11736
  const sourceRoot = sourceRootOf(context);
11448
- const file = path78.resolve(context.filename);
11737
+ const file = path79.resolve(context.filename);
11449
11738
  const segments = segmentsOf(file, sourceRoot);
11450
11739
  if (segments.length === 0) return {};
11451
11740
  const index = getProjectIndex(sourceRoot);
@@ -11584,11 +11873,11 @@ var uiStateRule = {
11584
11873
  };
11585
11874
 
11586
11875
  // eslint/rules/unknown-utility.ts
11587
- import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
11876
+ import { existsSync as existsSync13, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
11588
11877
  import { createRequire } from "module";
11589
- import path79 from "path";
11878
+ import path80 from "path";
11590
11879
  var PREFIX_NAMESPACES = {
11591
- bg: ["color"],
11880
+ bg: ["color", "background-image"],
11592
11881
  text: ["color", "text"],
11593
11882
  border: ["color"],
11594
11883
  ring: ["color"],
@@ -11867,7 +12156,7 @@ function stylesheetFiles(dir) {
11867
12156
  }
11868
12157
  return entries.flatMap((entry) => {
11869
12158
  if (entry.startsWith(".") || entry === "node_modules") return [];
11870
- const entryPath = path79.join(dir, entry);
12159
+ const entryPath = path80.join(dir, entry);
11871
12160
  let stats;
11872
12161
  try {
11873
12162
  stats = statSync6(entryPath);
@@ -11875,7 +12164,7 @@ function stylesheetFiles(dir) {
11875
12164
  return [];
11876
12165
  }
11877
12166
  if (stats.isDirectory()) return stylesheetFiles(entryPath);
11878
- return path79.extname(entry) === ".css" ? [entryPath] : [];
12167
+ return path80.extname(entry) === ".css" ? [entryPath] : [];
11879
12168
  });
11880
12169
  }
11881
12170
  function packageNameOf(specifier) {
@@ -11886,24 +12175,24 @@ function packageNameOf(specifier) {
11886
12175
  function resolvePackageStylesheet(fromFile, specifier) {
11887
12176
  const packageName = packageNameOf(specifier);
11888
12177
  if (!packageName || packageName === "tailwindcss") return void 0;
11889
- let directory = path79.dirname(fromFile);
11890
- while (directory !== path79.dirname(directory)) {
11891
- const packageRoot = path79.join(directory, "node_modules", packageName);
11892
- const manifestPath = path79.join(packageRoot, "package.json");
11893
- if (existsSync12(manifestPath)) {
12178
+ let directory = path80.dirname(fromFile);
12179
+ while (directory !== path80.dirname(directory)) {
12180
+ const packageRoot = path80.join(directory, "node_modules", packageName);
12181
+ const manifestPath = path80.join(packageRoot, "package.json");
12182
+ if (existsSync13(manifestPath)) {
11894
12183
  try {
11895
12184
  const manifest = readFileSync15(manifestPath, "utf8");
11896
12185
  const stylesheet = /"style"\s*:\s*"(?<file>[^"]+\.css)"/.exec(manifest)?.groups?.file;
11897
12186
  const main = /"main"\s*:\s*"(?<file>[^"]+\.css)"/.exec(manifest)?.groups?.file;
11898
12187
  const target = stylesheet ?? main;
11899
12188
  if (!target) return void 0;
11900
- const resolved = path79.resolve(packageRoot, target);
11901
- return existsSync12(resolved) ? resolved : void 0;
12189
+ const resolved = path80.resolve(packageRoot, target);
12190
+ return existsSync13(resolved) ? resolved : void 0;
11902
12191
  } catch {
11903
12192
  return void 0;
11904
12193
  }
11905
12194
  }
11906
- directory = path79.dirname(directory);
12195
+ directory = path80.dirname(directory);
11907
12196
  }
11908
12197
  return void 0;
11909
12198
  }
@@ -11994,8 +12283,12 @@ function readInventory(files) {
11994
12283
  for (const block of themeBlocks(css2)) {
11995
12284
  if (/--\*\s*:\s*initial\b/.test(block)) defaultsReset = true;
11996
12285
  for (const decl of block.matchAll(/--(?<namespace>[a-z][a-z0-9]*)-(?<token>[a-z0-9][a-z0-9_-]*)\s*:/g)) {
11997
- const namespace = decl.groups?.namespace;
11998
- const token = decl.groups?.token;
12286
+ let namespace = decl.groups?.namespace;
12287
+ let token = decl.groups?.token;
12288
+ if (namespace === "background" && token?.startsWith("image-")) {
12289
+ namespace = "background-image";
12290
+ token = token.slice("image-".length);
12291
+ }
11999
12292
  if (!namespace || !token) continue;
12000
12293
  let set = themeTokensByNamespace.get(namespace);
12001
12294
  if (!set) {
@@ -12159,7 +12452,7 @@ var unknownUtilityRule = {
12159
12452
  };
12160
12453
 
12161
12454
  // eslint/rules/util-file-name.ts
12162
- import path80 from "path";
12455
+ import path81 from "path";
12163
12456
  function toKebabCase2(value) {
12164
12457
  return value.replace(/(?<lower>[a-z0-9])(?<upper>[A-Z])/g, "$<lower>-$<upper>").replace(/(?<first>[A-Z])(?<rest>[A-Z][a-z])/g, "$<first>-$<rest>").toLowerCase();
12165
12458
  }
@@ -12172,7 +12465,7 @@ var utilFileNameRule = {
12172
12465
  }
12173
12466
  },
12174
12467
  create(context) {
12175
- const filename = path80.resolve(context.filename);
12468
+ const filename = path81.resolve(context.filename);
12176
12469
  const segments = filename.replace(/\\/g, "/").split("/");
12177
12470
  if (!segments.includes("utils")) return {};
12178
12471
  let module;
@@ -12186,13 +12479,13 @@ var utilFileNameRule = {
12186
12479
  const functionName = functions[0]?.name;
12187
12480
  if (!functionName) return {};
12188
12481
  const expected = toKebabCase2(functionName);
12189
- const actual = path80.basename(filename, path80.extname(filename));
12482
+ const actual = path81.basename(filename, path81.extname(filename));
12190
12483
  if (!expected || actual === expected) return {};
12191
12484
  return {
12192
12485
  Program(node) {
12193
12486
  context.report({
12194
12487
  node,
12195
- message: `A utility file exporting ${functionName} must be named ${expected}.${path80.extname(filename).slice(1)}.`
12488
+ message: `A utility file exporting ${functionName} must be named ${expected}.${path81.extname(filename).slice(1)}.`
12196
12489
  });
12197
12490
  }
12198
12491
  };
@@ -12200,7 +12493,7 @@ var utilFileNameRule = {
12200
12493
  };
12201
12494
 
12202
12495
  // eslint/rules/value-extraction.ts
12203
- import path81 from "path";
12496
+ import path82 from "path";
12204
12497
  var valueExtractionRule = {
12205
12498
  meta: {
12206
12499
  schema: [],
@@ -12211,7 +12504,7 @@ var valueExtractionRule = {
12211
12504
  },
12212
12505
  create(context) {
12213
12506
  const sourceRoot = sourceRootOf(context);
12214
- const file = path81.resolve(context.filename);
12507
+ const file = path82.resolve(context.filename);
12215
12508
  const segments = segmentsOf(file, sourceRoot);
12216
12509
  if (segments.length === 0 || segments[0] !== "app") return {};
12217
12510
  const index = getProjectIndex(sourceRoot);
@@ -12232,8 +12525,8 @@ var valueExtractionRule = {
12232
12525
  };
12233
12526
 
12234
12527
  // eslint/rules/vulyk/tracked-docs.ts
12235
- import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
12236
- import path82 from "path";
12528
+ import { existsSync as existsSync14, readFileSync as readFileSync16 } from "fs";
12529
+ import path83 from "path";
12237
12530
  var PASIKA_REPO = "Bredansky/pasika";
12238
12531
  var BASE_REQUIRED_TRACKED_DOCS = [
12239
12532
  { name: "documentation-guide", path: "docs/documentation-guide" },
@@ -12266,9 +12559,9 @@ var trackedDocsRule = {
12266
12559
  if (!context.filename.endsWith("package.json")) return;
12267
12560
  const root = node.body;
12268
12561
  if (root.type !== "Object") return;
12269
- const projectRoot = path82.dirname(path82.resolve(context.filename));
12270
- const configPath = path82.join(projectRoot, "vulyk.config.ts");
12271
- if (!existsSync13(configPath)) {
12562
+ const projectRoot = path83.dirname(path83.resolve(context.filename));
12563
+ const configPath = path83.join(projectRoot, "vulyk.config.ts");
12564
+ if (!existsSync14(configPath)) {
12272
12565
  context.report({
12273
12566
  node,
12274
12567
  message: "No vulyk.config.ts found. Run npx vulyk init to create one that tracks the framework's required tracked docs."
@@ -12292,8 +12585,8 @@ var trackedDocsRule = {
12292
12585
  });
12293
12586
  }
12294
12587
  }
12295
- const agentsPath = path82.join(projectRoot, "AGENTS.md");
12296
- if (!existsSync13(agentsPath)) {
12588
+ const agentsPath = path83.join(projectRoot, "AGENTS.md");
12589
+ if (!existsSync14(agentsPath)) {
12297
12590
  context.report({
12298
12591
  node,
12299
12592
  message: "No AGENTS.md found. Run npx vulyk agents to generate the agent file that routes to the tracked docs."
@@ -12392,7 +12685,7 @@ var withResponseHelperRule = {
12392
12685
 
12393
12686
  // eslint/rules/zirka-baseline.ts
12394
12687
  import fs7 from "fs";
12395
- import path83 from "path";
12688
+ import path84 from "path";
12396
12689
  var ESLINT_CONFIG = /^eslint\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
12397
12690
  var PRETTIER_CONFIGS = [
12398
12691
  "prettier.config.mjs",
@@ -12411,10 +12704,10 @@ var zirkaBaselineRule = {
12411
12704
  }
12412
12705
  },
12413
12706
  create(context) {
12414
- const filename = path83.resolve(context.filename);
12415
- const basename = path83.basename(filename);
12707
+ const filename = path84.resolve(context.filename);
12708
+ const basename = path84.basename(filename);
12416
12709
  if (!ESLINT_CONFIG.test(basename)) return {};
12417
- const projectRoot = path83.dirname(filename);
12710
+ const projectRoot = path84.dirname(filename);
12418
12711
  const report3 = (message) => {
12419
12712
  context.report({
12420
12713
  node: context.sourceCode.ast,
@@ -12429,7 +12722,7 @@ var zirkaBaselineRule = {
12429
12722
  'ESLint config must take its configuration from zirka (import { styleguide } from "zirka") instead of restating rules locally.'
12430
12723
  );
12431
12724
  }
12432
- const tsconfigPath = path83.join(projectRoot, "tsconfig.json");
12725
+ const tsconfigPath = path84.join(projectRoot, "tsconfig.json");
12433
12726
  if (!fs7.existsSync(tsconfigPath)) {
12434
12727
  report3('No tsconfig.json found. Create one extending the zirka TypeScript base config ("zirka/typescript").');
12435
12728
  } else {
@@ -12447,13 +12740,13 @@ var zirkaBaselineRule = {
12447
12740
  report3('tsconfig.json must extend the zirka TypeScript base config ("zirka/typescript").');
12448
12741
  }
12449
12742
  }
12450
- const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs7.existsSync(path83.join(projectRoot, name)));
12743
+ const prettierConfigFile = PRETTIER_CONFIGS.find((name) => fs7.existsSync(path84.join(projectRoot, name)));
12451
12744
  if (!prettierConfigFile) {
12452
12745
  report3(
12453
12746
  "No prettier config found. Create one that takes its configuration from zirka (styleguide({ prettier: true }).prettierConfig)."
12454
12747
  );
12455
12748
  } else {
12456
- const content = fs7.readFileSync(path83.join(projectRoot, prettierConfigFile), "utf8");
12749
+ const content = fs7.readFileSync(path84.join(projectRoot, prettierConfigFile), "utf8");
12457
12750
  if (!content.includes("zirka")) {
12458
12751
  report3(
12459
12752
  "The prettier config must take its configuration from zirka (styleguide({ prettier: true }).prettierConfig) instead of restating it locally."
@@ -12789,7 +13082,7 @@ var tailwindStructureRules = {
12789
13082
  languageOptions: { tolerant: true },
12790
13083
  rules: Object.fromEntries(
12791
13084
  tailwindRuleIds.filter(
12792
- (id) => id !== "pasika/css-entry-point" && id !== "pasika/feature-css-placement" && id !== "pasika/unique-css-definition" && id !== "pasika/utility-mapping" && id !== "pasika/no-utility-alias" && id !== "pasika/root-variables" && id !== "pasika/color-role-usage"
13085
+ (id) => id !== "pasika/css-entry-point" && id !== "pasika/feature-css-placement" && id !== "pasika/unique-css-definition" && id !== "pasika/utility-mapping" && id !== "pasika/utility-name-parity" && id !== "pasika/no-utility-alias" && id !== "pasika/root-variables" && id !== "pasika/color-role-usage"
12793
13086
  ).map((id) => [id, "error"])
12794
13087
  )
12795
13088
  };
@@ -12807,6 +13100,7 @@ var tailwindImportGraph = {
12807
13100
  "pasika/unique-css-definition": "error",
12808
13101
  "pasika/unique-css-token-value": "error",
12809
13102
  "pasika/utility-mapping": "error",
13103
+ "pasika/utility-name-parity": "error",
12810
13104
  "pasika/no-utility-alias": "error",
12811
13105
  "pasika/root-variables": "error",
12812
13106
  "pasika/color-role-usage": "error"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.10.117",
3
+ "version": "0.10.119",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",
@@ -90,6 +90,7 @@
90
90
  "next": "16.3.5",
91
91
  "prettier": "3.8.1",
92
92
  "tailwind-merge": "3.7.0",
93
+ "tailwindcss": "4.3.3",
93
94
  "tsup": "8.5.1",
94
95
  "tsx": "4.23.12",
95
96
  "typescript": "6.0.3",
@@ -105,6 +106,7 @@
105
106
  "clsx": ">=2.0.0",
106
107
  "next": ">=15.0.0",
107
108
  "tailwind-merge": ">=3.0.0",
109
+ "tailwindcss": "^4.0.0",
108
110
  "zod": ">=4.0.0"
109
111
  },
110
112
  "peerDependenciesMeta": {
@@ -117,6 +119,9 @@
117
119
  "tailwind-merge": {
118
120
  "optional": true
119
121
  },
122
+ "tailwindcss": {
123
+ "optional": true
124
+ },
120
125
  "zod": {
121
126
  "optional": true
122
127
  }