pasika 0.6.1 → 0.7.1

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/README.md CHANGED
@@ -191,7 +191,7 @@ Applied to `src/**/globals.css` (and other stylesheets) through `@eslint/css` wi
191
191
  | `pasika/stylesheet-ordering` | Imports → `@custom-variant` → `:root` → `@theme` → `@utility` → `@layer base` |
192
192
  | `pasika/css-variable-naming` | Background vars named `--<role>-canvas`, text vars `--<role>-ink` |
193
193
  | `pasika/custom-utility-apply` | `@utility` blocks use `@apply` |
194
- | `pasika/surface-utility` | Repeated canvas+ink combos become a named surface utility |
194
+ | `pasika/skin-utility` | Repeated style combinations become a named skin utility |
195
195
  | `pasika/theme-variable-namespace` | Utility class groups share a namespace prefix |
196
196
  | `pasika/css-entry-point` † | One global entry, imported by one module, project CSS only in a stylesheet the entry imports directly |
197
197
  | `pasika/unused-utility` | A custom `@utility` no source file references is reported as dead |
@@ -109,7 +109,7 @@ declare const tailwindRules: {
109
109
  "stylesheet-ordering": _eslint_css.CSSRuleDefinition;
110
110
  "css-variable-naming": _eslint_css.CSSRuleDefinition;
111
111
  "custom-utility-apply": _eslint_css.CSSRuleDefinition;
112
- "surface-utility": _eslint_css.CSSRuleDefinition;
112
+ "skin-utility": _eslint_css.CSSRuleDefinition;
113
113
  "theme-variable-namespace": _eslint_css.CSSRuleDefinition;
114
114
  "css-entry-point": _eslint_css.CSSRuleDefinition;
115
115
  "global-stylesheet": _eslint_css.CSSRuleDefinition;
@@ -159,7 +159,7 @@ declare const pasikaPlugin: {
159
159
  "stylesheet-ordering": _eslint_css.CSSRuleDefinition;
160
160
  "css-variable-naming": _eslint_css.CSSRuleDefinition;
161
161
  "custom-utility-apply": _eslint_css.CSSRuleDefinition;
162
- "surface-utility": _eslint_css.CSSRuleDefinition;
162
+ "skin-utility": _eslint_css.CSSRuleDefinition;
163
163
  "theme-variable-namespace": _eslint_css.CSSRuleDefinition;
164
164
  "css-entry-point": _eslint_css.CSSRuleDefinition;
165
165
  "global-stylesheet": _eslint_css.CSSRuleDefinition;
@@ -522,7 +522,7 @@ var noArbitraryTailwindRule = {
522
522
  };
523
523
 
524
524
  // eslint/rules/unknown-utility.ts
525
- import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
525
+ import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
526
526
  import path5 from "path";
527
527
  var PREFIX_NAMESPACES = {
528
528
  bg: ["color"],
@@ -749,6 +749,53 @@ function stylesheetFiles(dir) {
749
749
  return path5.extname(entry) === ".css" ? [entryPath] : [];
750
750
  });
751
751
  }
752
+ function packageNameOf(specifier) {
753
+ if (specifier.startsWith(".") || specifier.startsWith("/")) return void 0;
754
+ const segments = specifier.split("/");
755
+ return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
756
+ }
757
+ function resolvePackageStylesheet(fromFile, specifier) {
758
+ const packageName = packageNameOf(specifier);
759
+ if (!packageName || packageName === "tailwindcss") return void 0;
760
+ let directory = path5.dirname(fromFile);
761
+ while (directory !== path5.dirname(directory)) {
762
+ const packageRoot = path5.join(directory, "node_modules", packageName);
763
+ const manifestPath = path5.join(packageRoot, "package.json");
764
+ if (existsSync(manifestPath)) {
765
+ try {
766
+ const manifest = readFileSync2(manifestPath, "utf8");
767
+ const stylesheet = /"style"\s*:\s*"(?<file>[^"]+\.css)"/.exec(manifest)?.groups?.file;
768
+ const main = /"main"\s*:\s*"(?<file>[^"]+\.css)"/.exec(manifest)?.groups?.file;
769
+ const target = stylesheet ?? main;
770
+ if (!target) return void 0;
771
+ const resolved = path5.resolve(packageRoot, target);
772
+ return existsSync(resolved) ? resolved : void 0;
773
+ } catch {
774
+ return void 0;
775
+ }
776
+ }
777
+ directory = path5.dirname(directory);
778
+ }
779
+ return void 0;
780
+ }
781
+ function inventoryStylesheetFiles(projectFiles) {
782
+ const files = new Set(projectFiles);
783
+ for (const file of projectFiles) {
784
+ let css2;
785
+ try {
786
+ css2 = readFileSync2(file, "utf8");
787
+ } catch {
788
+ continue;
789
+ }
790
+ for (const match of css2.matchAll(/@import\s+(?:url\(\s*)?["'](?<specifier>[^"']+)["']/gi)) {
791
+ const specifier = match.groups?.specifier;
792
+ if (!specifier) continue;
793
+ const imported = resolvePackageStylesheet(file, specifier);
794
+ if (imported) files.add(imported);
795
+ }
796
+ }
797
+ return [...files];
798
+ }
752
799
  function themeBlocks(css2) {
753
800
  const blocks = [];
754
801
  let index = 0;
@@ -794,6 +841,7 @@ function classSelectors(css2) {
794
841
  }
795
842
  function readInventory(files) {
796
843
  const utilities = /* @__PURE__ */ new Set();
844
+ const wildcardUtilities = /* @__PURE__ */ new Set();
797
845
  const utilityPrefixes = /* @__PURE__ */ new Set();
798
846
  const themeTokensByNamespace = /* @__PURE__ */ new Map();
799
847
  const plainClasses = /* @__PURE__ */ new Set();
@@ -805,9 +853,10 @@ function readInventory(files) {
805
853
  } catch {
806
854
  continue;
807
855
  }
808
- for (const name of css2.matchAll(/@utility\s+(?<utilityName>[a-zA-Z0-9_-]+)/g)) {
856
+ for (const name of css2.matchAll(/@utility\s+(?<utilityName>[a-zA-Z0-9_*-]+)/g)) {
809
857
  const utilityName = name.groups?.utilityName;
810
858
  if (!utilityName) continue;
859
+ if (utilityName.endsWith("-*")) wildcardUtilities.add(utilityName.slice(0, -1));
811
860
  utilities.add(utilityName);
812
861
  const dash = utilityName.indexOf("-");
813
862
  if (dash > 0) utilityPrefixes.add(utilityName.slice(0, dash));
@@ -828,7 +877,14 @@ function readInventory(files) {
828
877
  }
829
878
  }
830
879
  }
831
- return { utilities, utilityPrefixes, themeTokens: themeTokensByNamespace, defaultsReset, plainClasses };
880
+ return {
881
+ utilities,
882
+ wildcardUtilities,
883
+ utilityPrefixes,
884
+ themeTokens: themeTokensByNamespace,
885
+ defaultsReset,
886
+ plainClasses
887
+ };
832
888
  }
833
889
  function isColorToken(token, inventory) {
834
890
  if (inventory.themeTokens.get("color")?.has(token)) return true;
@@ -852,6 +908,7 @@ function isKnown(className, inventory) {
852
908
  const token = compound ? utility.slice(compound.length + 1) : utility.slice(dash + 1);
853
909
  if (!token) return true;
854
910
  if (inventory.utilities.has(utility)) return true;
911
+ if ([...inventory.wildcardUtilities].some((wildcardPrefix) => utility.startsWith(wildcardPrefix))) return true;
855
912
  const namespaces = PREFIX_NAMESPACES[prefix];
856
913
  if (!namespaces) {
857
914
  if (STATIC_VALUE_TOKENS[prefix]?.includes(token)) return true;
@@ -890,7 +947,7 @@ var unknownUtilityRule = {
890
947
  let inventory;
891
948
  try {
892
949
  if (!statSync(sourceRoot).isDirectory()) return {};
893
- inventory = readInventory(stylesheetFiles(sourceRoot));
950
+ inventory = readInventory(inventoryStylesheetFiles(stylesheetFiles(sourceRoot)));
894
951
  } catch {
895
952
  return {};
896
953
  }
@@ -3681,11 +3738,7 @@ function combosFor(index) {
3681
3738
  const files = [...index.modules.keys()].filter((file) => file.endsWith(".tsx") || file.endsWith(".jsx")).sort((left, right) => left.localeCompare(right));
3682
3739
  let fingerprint2 = "";
3683
3740
  for (const file of files) {
3684
- try {
3685
- fingerprint2 += String(statSync3(file).mtimeMs);
3686
- } catch {
3687
- fingerprint2 += "0";
3688
- }
3741
+ fingerprint2 += String(statSync3(file).mtimeMs);
3689
3742
  }
3690
3743
  if (comboCache?.sourceRoot === index.sourceRoot && comboCache.fingerprint === fingerprint2) {
3691
3744
  return comboCache.combos;
@@ -3710,7 +3763,7 @@ var sharedStyleDedupRule = {
3710
3763
  schema: [],
3711
3764
  type: "problem",
3712
3765
  docs: {
3713
- description: "Require a className combination used by two or more components to become a named custom utility."
3766
+ description: "Require a className combination used by two or more components to become a named *-skin utility."
3714
3767
  }
3715
3768
  },
3716
3769
  create(context) {
@@ -3729,7 +3782,7 @@ var sharedStyleDedupRule = {
3729
3782
  context.report({
3730
3783
  node,
3731
3784
  loc: { line: 1, column: 0 },
3732
- message: `Class combination "${combo}" is used by ${String(users.size)} components and should change together; create a named custom Tailwind utility for it. See docs/next-tailwind-guide/rules/theme-and-utility-definition-rule.md`
3785
+ message: `Class combination "${combo}" is used by ${String(users.size)} components and should change together; create a named *-skin Tailwind utility for it. See docs/next-tailwind-guide/rules/theme-and-utility-definition-rule.md`
3733
3786
  });
3734
3787
  }
3735
3788
  }
@@ -5019,7 +5072,7 @@ var glossaryTermLinkingRule = {
5019
5072
  };
5020
5073
 
5021
5074
  // eslint/rules/documentation/guide-mentions-documents.ts
5022
- import { existsSync } from "fs";
5075
+ import { existsSync as existsSync2 } from "fs";
5023
5076
  import path43 from "path";
5024
5077
  function visitSteps3(node, check) {
5025
5078
  if (node.type === "list" && node.ordered) {
@@ -5086,7 +5139,7 @@ var guideMentionsDocumentsRule = {
5086
5139
  const target = linkTarget(link.url);
5087
5140
  if (!target.endsWith(".md")) continue;
5088
5141
  const resolved = path43.normalize(path43.join(guideDir, target));
5089
- if (!existsSync(resolved)) {
5142
+ if (!existsSync2(resolved)) {
5090
5143
  context.report({
5091
5144
  node: link,
5092
5145
  message: `Guide links a document that does not exist: ${link.url}`
@@ -5441,12 +5494,45 @@ var stylesheetOrderingRule = {
5441
5494
  };
5442
5495
 
5443
5496
  // eslint/rules/tailwind/css-variable-naming.ts
5497
+ var EDGE_PROPERTY_WORDS = /* @__PURE__ */ new Set(["border", "outline", "ring", "stroke"]);
5498
+ var NON_COLOR_WORDS = /* @__PURE__ */ new Set(["height", "leading", "offset", "radius", "size", "spacing", "style", "width"]);
5499
+ var TAILWIND_THEME_NAMESPACES = /* @__PURE__ */ new Set([
5500
+ "animate",
5501
+ "aspect",
5502
+ "blur",
5503
+ "breakpoint",
5504
+ "color",
5505
+ "container",
5506
+ "drop",
5507
+ "ease",
5508
+ "font",
5509
+ "inset",
5510
+ "leading",
5511
+ "perspective",
5512
+ "radius",
5513
+ "shadow",
5514
+ "spacing",
5515
+ "text",
5516
+ "tracking"
5517
+ ]);
5518
+ function isTailwindThemeVariable(property) {
5519
+ const namespace = property.slice(2).split("-")[0];
5520
+ return namespace !== void 0 && TAILWIND_THEME_NAMESPACES.has(namespace);
5521
+ }
5522
+ function looksLikeEdgeColor(lower) {
5523
+ const words = lower.slice(2).split("-");
5524
+ return words.some((word) => EDGE_PROPERTY_WORDS.has(word)) && !words.some((word) => NON_COLOR_WORDS.has(word));
5525
+ }
5526
+ function looksLikeTextColor(lower) {
5527
+ const words = lower.slice(2).split("-");
5528
+ return words.includes("text") && !words.some((word) => NON_COLOR_WORDS.has(word));
5529
+ }
5444
5530
  var cssVariableNamingRule = {
5445
5531
  meta: {
5446
5532
  schema: [],
5447
5533
  type: "problem",
5448
5534
  docs: {
5449
- description: "Require background variables to end in -canvas and text variables to end in -ink."
5535
+ description: "Require property-specific color variables to use canvas, ink, and edge role suffixes."
5450
5536
  }
5451
5537
  },
5452
5538
  create(context) {
@@ -5454,6 +5540,7 @@ var cssVariableNamingRule = {
5454
5540
  Declaration(node) {
5455
5541
  const property = node.property;
5456
5542
  if (!property.startsWith("--")) return;
5543
+ if (isTailwindThemeVariable(property)) return;
5457
5544
  const lower = property.toLowerCase();
5458
5545
  if ((lower.includes("bg") || lower.includes("background")) && !lower.endsWith("-canvas")) {
5459
5546
  context.report({
@@ -5461,12 +5548,18 @@ var cssVariableNamingRule = {
5461
5548
  message: `CSS variable "${property}" looks like a background token; name it --<role>-canvas instead.`
5462
5549
  });
5463
5550
  }
5464
- if (lower.includes("text") && !lower.endsWith("-ink")) {
5551
+ if (looksLikeTextColor(lower) && !lower.endsWith("-ink")) {
5465
5552
  context.report({
5466
5553
  node,
5467
5554
  message: `CSS variable "${property}" looks like a text token; name it --<role>-ink instead.`
5468
5555
  });
5469
5556
  }
5557
+ if (looksLikeEdgeColor(lower) && !lower.endsWith("-edge")) {
5558
+ context.report({
5559
+ node,
5560
+ message: `CSS variable "${property}" looks like a boundary token; name it --<role>-edge instead.`
5561
+ });
5562
+ }
5470
5563
  }
5471
5564
  };
5472
5565
  }
@@ -5514,24 +5607,31 @@ var customUtilityApplyRule = {
5514
5607
  }
5515
5608
  };
5516
5609
 
5517
- // eslint/rules/tailwind/surface-utility.ts
5518
- var surfaceUtilityRule = {
5610
+ // eslint/rules/tailwind/skin-utility.ts
5611
+ var skinUtilityRule = {
5519
5612
  meta: {
5520
5613
  schema: [],
5521
5614
  type: "problem",
5522
5615
  docs: {
5523
- description: "Require repeated canvas+ink combinations to become a *-surface utility."
5616
+ description: "Require reusable style combinations to become *-skin utilities."
5524
5617
  }
5525
5618
  },
5526
5619
  create(context) {
5527
5620
  return {
5528
5621
  "StyleSheet:exit"(node) {
5622
+ for (const utility of atrulesNamed(node, "utility")) {
5623
+ const name = preludeIdentifiers(utility)[0];
5624
+ const applied = new Set(atrulesNamed(utility, "apply").flatMap((apply) => preludeIdentifiers(apply)));
5625
+ if (applied.size < 2 || name?.endsWith("-skin") === true) continue;
5626
+ context.report({
5627
+ node: utility,
5628
+ message: `Custom utility "${name ?? "unknown"}" combines ${String(applied.size)} styles; use the *-skin suffix.`
5629
+ });
5630
+ }
5529
5631
  const combinations = /* @__PURE__ */ new Map();
5530
5632
  for (const apply of atrulesNamed(node, "apply")) {
5531
5633
  const classes = preludeIdentifiers(apply);
5532
- if (!classes.some((name) => name.includes("canvas")) || !classes.some((name) => name.includes("ink"))) {
5533
- continue;
5534
- }
5634
+ if (new Set(classes).size < 2) continue;
5535
5635
  const key = [...classes].sort().join(" ");
5536
5636
  combinations.set(key, (combinations.get(key) ?? 0) + 1);
5537
5637
  }
@@ -5539,7 +5639,7 @@ var surfaceUtilityRule = {
5539
5639
  if (count < 2) continue;
5540
5640
  context.report({
5541
5641
  node,
5542
- message: `Combination "${combo}" appears ${String(count)} times. Create a *-surface custom Tailwind utility for it.`
5642
+ message: `Combination "${combo}" appears ${String(count)} times. Create a *-skin custom Tailwind utility for it.`
5543
5643
  });
5544
5644
  }
5545
5645
  }
@@ -5839,7 +5939,7 @@ var tailwindRules = {
5839
5939
  "stylesheet-ordering": stylesheetOrderingRule,
5840
5940
  "css-variable-naming": cssVariableNamingRule,
5841
5941
  "custom-utility-apply": customUtilityApplyRule,
5842
- "surface-utility": surfaceUtilityRule,
5942
+ "skin-utility": skinUtilityRule,
5843
5943
  "theme-variable-namespace": themeVariableNamespaceRule,
5844
5944
  "css-entry-point": cssEntryPointRule,
5845
5945
  "global-stylesheet": globalStylesheetRule,
@@ -6083,7 +6183,7 @@ var nextjsStackRule = {
6083
6183
  };
6084
6184
 
6085
6185
  // eslint/rules/package-json/vitest-coverage.ts
6086
- import { existsSync as existsSync2, readFileSync as readFileSync9 } from "fs";
6186
+ import { existsSync as existsSync3, readFileSync as readFileSync9 } from "fs";
6087
6187
  import path47 from "path";
6088
6188
  function memberName5(member) {
6089
6189
  return member.name.type === "String" ? member.name.value : member.name.name;
@@ -6159,7 +6259,7 @@ var vitestCoverageRule = {
6159
6259
  message: 'package.json must declare a "test:unit:coverage" script that runs Vitest with coverage.'
6160
6260
  });
6161
6261
  }
6162
- const configName = VITEST_CONFIG_NAMES.find((name) => existsSync2(path47.join(context.cwd, name)));
6262
+ const configName = VITEST_CONFIG_NAMES.find((name) => existsSync3(path47.join(context.cwd, name)));
6163
6263
  if (!configName) {
6164
6264
  context.report({
6165
6265
  node,
@@ -6224,7 +6324,7 @@ var nextjsPackageJsonRules = {
6224
6324
  };
6225
6325
 
6226
6326
  // eslint/rules/husky/husky-hook.ts
6227
- import { existsSync as existsSync3, readFileSync as readFileSync10 } from "fs";
6327
+ import { existsSync as existsSync4, readFileSync as readFileSync10 } from "fs";
6228
6328
  import path48 from "path";
6229
6329
  function memberName6(member) {
6230
6330
  return member.name.type === "String" ? member.name.value : member.name.name;
@@ -6244,7 +6344,7 @@ var huskyHookRule = {
6244
6344
  if (root.type !== "Object") return;
6245
6345
  if (!context.filename.endsWith("package.json")) return;
6246
6346
  const hookPath = path48.join(context.cwd, ".husky", "pre-commit");
6247
- if (!existsSync3(hookPath)) {
6347
+ if (!existsSync4(hookPath)) {
6248
6348
  context.report({
6249
6349
  node,
6250
6350
  message: "No .husky/pre-commit hook found. Configure husky to run checks before commits."
@@ -6270,7 +6370,7 @@ var huskyHookRule = {
6270
6370
  context.report({ node, message: ".husky/pre-commit must run npx libyear --limit-major-individual=1." });
6271
6371
  }
6272
6372
  const suppressionsPath = path48.join(context.cwd, "eslint-suppressions.json");
6273
- if (existsSync3(suppressionsPath)) {
6373
+ if (existsSync4(suppressionsPath)) {
6274
6374
  requireNamedScript("lint:prune");
6275
6375
  }
6276
6376
  if (scripts?.value.type === "Object") {
@@ -6293,7 +6393,7 @@ var huskyRules = {
6293
6393
  };
6294
6394
 
6295
6395
  // eslint/rules/vulyk/vulyk-docs.ts
6296
- import { existsSync as existsSync4, readFileSync as readFileSync11 } from "fs";
6396
+ import { existsSync as existsSync5, readFileSync as readFileSync11 } from "fs";
6297
6397
  import path49 from "path";
6298
6398
  var PASIKA_REPO = "Bredansky/pasika";
6299
6399
  var BASE_REQUIRED_DOCS = [
@@ -6329,7 +6429,7 @@ var vulykDocsRule = {
6329
6429
  if (root.type !== "Object") return;
6330
6430
  const projectRoot = path49.dirname(path49.resolve(context.filename));
6331
6431
  const configPath = path49.join(projectRoot, "vulyk.config.ts");
6332
- if (!existsSync4(configPath)) {
6432
+ if (!existsSync5(configPath)) {
6333
6433
  context.report({
6334
6434
  node,
6335
6435
  message: "No vulyk.config.ts found. Run npx vulyk@latest init to create one that tracks the framework's docs."
@@ -6354,7 +6454,7 @@ var vulykDocsRule = {
6354
6454
  }
6355
6455
  }
6356
6456
  const agentsPath = path49.join(projectRoot, "AGENTS.md");
6357
- if (!existsSync4(agentsPath)) {
6457
+ if (!existsSync5(agentsPath)) {
6358
6458
  context.report({
6359
6459
  node,
6360
6460
  message: "No AGENTS.md found. Run npx vulyk@latest agents to generate the agent file that routes to the tracked docs."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pasika",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "Reusable agent setup package",
5
5
  "repository": {
6
6
  "type": "git",