rainbowindex 0.4.1 → 0.5.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.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Oxlint plugin for Rainbow Index projects.
3
+ *
4
+ * Oxlint loads a JS plugin by module specifier and reads its default export,
5
+ * so this entry keeps a default export even though the rest of the package
6
+ * prefers named ones. Register it from a Vite+ `vite.config.ts`:
7
+ *
8
+ * ```ts
9
+ * export default defineConfig({
10
+ * lint: {
11
+ * jsPlugins: [{ name: "rainbowindex", specifier: "rainbowindex/oxlint" }],
12
+ * rules: { "rainbowindex/prefer-ri": "error" },
13
+ * },
14
+ * });
15
+ * ```
16
+ *
17
+ * The types below describe only the slice of the Oxlint rule API this plugin
18
+ * touches. They are declared here rather than imported from `@oxlint/plugins`
19
+ * because that package is a transitive dependency of Oxlint that a consumer
20
+ * cannot resolve, and its `definePlugin` / `defineRule` helpers are identity
21
+ * functions with no runtime behavior to reuse.
22
+ */
23
+ interface ImportDeclarationNode {
24
+ source: {
25
+ value: string;
26
+ };
27
+ }
28
+ interface RuleContext {
29
+ report(diagnostic: {
30
+ message: string;
31
+ node: ImportDeclarationNode;
32
+ }): void;
33
+ }
34
+ interface RuleVisitor {
35
+ ImportDeclaration(node: ImportDeclarationNode): void;
36
+ }
37
+ interface OxlintRule {
38
+ meta: {
39
+ type: "suggestion";
40
+ docs: {
41
+ description: string;
42
+ };
43
+ };
44
+ create(context: RuleContext): RuleVisitor;
45
+ }
46
+ interface OxlintPlugin {
47
+ meta: {
48
+ name: string;
49
+ };
50
+ rules: Record<string, OxlintRule>;
51
+ }
52
+ declare const preferRiRule: OxlintRule;
53
+ declare const plugin: OxlintPlugin;
54
+
55
+ export { type OxlintPlugin, type OxlintRule, plugin as default, plugin, preferRiRule };
@@ -0,0 +1,38 @@
1
+ // src/integrations/oxlint.ts
2
+ var REPLACED_PACKAGES = {
3
+ clsx: "composes conditional classes",
4
+ classnames: "composes conditional classes",
5
+ "tailwind-merge": "resolves class conflicts"
6
+ };
7
+ function packageName(specifier) {
8
+ return specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0] ?? "";
9
+ }
10
+ var preferRiRule = {
11
+ meta: {
12
+ type: "suggestion",
13
+ docs: { description: "Merge class names with ri() from rainbowindex." }
14
+ },
15
+ create(context) {
16
+ return {
17
+ ImportDeclaration(node) {
18
+ const name = packageName(node.source.value);
19
+ const job = REPLACED_PACKAGES[name];
20
+ if (!job) return;
21
+ context.report({
22
+ message: `Import { ri } from "rainbowindex" instead of "${name}". ri() ${job}, and it reads the same theme the compiler emits.`,
23
+ node
24
+ });
25
+ }
26
+ };
27
+ }
28
+ };
29
+ var plugin = {
30
+ meta: { name: "rainbowindex" },
31
+ rules: { "prefer-ri": preferRiRule }
32
+ };
33
+ var oxlint_default = plugin;
34
+ export {
35
+ oxlint_default as default,
36
+ plugin,
37
+ preferRiRule
38
+ };
@@ -1,4 +1,4 @@
1
- import { b as CompilationSnapshot } from './context-ruu2x_jR.js';
1
+ import { b as CompilationSnapshot } from './context-B9yhJxd5.js';
2
2
 
3
3
  /**
4
4
  * ri() — class merge function.
@@ -51,19 +51,6 @@ declare function ri(...inputs: ClassInput[]): string;
51
51
  */
52
52
  declare function createRi(snapshot?: CompilationSnapshot): (...inputs: ClassInput[]) => string;
53
53
 
54
- /**
55
- * Claim resolution for ri() — utility name → CSS properties it sets.
56
- *
57
- * Split from merge/index.ts so each merge file is one concept: this file owns
58
- * the dual-mode dispatch tables and resolvePropsWith(); index.ts owns the
59
- * merge algorithm; context.ts owns the compilation-context lifecycle.
60
- *
61
- * Everything here is immutable module-init data plus pure closures over it —
62
- * the mutable published compilation state lives in context.ts, and callers
63
- * thread it in through resolvePropsWith()'s parameters.
64
- */
65
- declare const DEFAULT_TEXT_SIZES: readonly ["xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl", "5xl"];
66
-
67
54
  /**
68
55
  * `safelist()` — declare utility classes that must be emitted regardless of
69
56
  * whether the consumer's source files reference them directly.
@@ -106,4 +93,4 @@ declare const DEFAULT_TEXT_SIZES: readonly ["xs", "sm", "base", "lg", "xl", "2xl
106
93
  */
107
94
  declare function safelist(...parts: ReadonlyArray<string | false | null | undefined>): string;
108
95
 
109
- export { DEFAULT_TEXT_SIZES as D, createRi as c, ri as r, safelist as s };
96
+ export { createRi as c, ri as r, safelist as s };
package/dist/vite.mjs CHANGED
@@ -1,21 +1,26 @@
1
1
  import {
2
2
  postcss_default
3
- } from "./chunk-IYIUS7AE.mjs";
4
- import "./chunk-53WD6U2X.mjs";
3
+ } from "./chunk-4CTJLMYM.mjs";
4
+ import {
5
+ enableSourceFileListCache,
6
+ invalidateSourceFileListCache
7
+ } from "./chunk-F4VCBISU.mjs";
5
8
  import {
6
9
  isSourceFile
7
10
  } from "./chunk-3HRMFZGE.mjs";
8
11
  import {
9
12
  DIRECTIVE_TYPE_NAMES,
13
+ codepointCompare,
10
14
  expandApplyGroups,
15
+ extractClassesFromSource,
11
16
  findClosingBrace,
12
17
  hasRIActivation,
13
18
  isAtRuleBoundary,
14
19
  isAtRuleNameChar
15
- } from "./chunk-6GTG5SZJ.mjs";
20
+ } from "./chunk-PDORZSQX.mjs";
16
21
  import {
17
22
  devWarn
18
- } from "./chunk-KRZL4IDK.mjs";
23
+ } from "./chunk-4UKFK2GE.mjs";
19
24
 
20
25
  // src/integrations/vite.ts
21
26
  import { existsSync } from "fs";
@@ -162,6 +167,7 @@ function rainbowindexVite() {
162
167
  let logger;
163
168
  const riCSSFiles = /* @__PURE__ */ new Set();
164
169
  const fileVersions = /* @__PURE__ */ new Map();
170
+ const candidateSignatures = /* @__PURE__ */ new Map();
165
171
  let hotUpdateCount = 0;
166
172
  const PRUNE_INTERVAL = 50;
167
173
  return {
@@ -169,19 +175,27 @@ function rainbowindexVite() {
169
175
  enforce: "pre",
170
176
  async config(config) {
171
177
  root = config.root ?? process.cwd();
178
+ const patch = {};
179
+ const ignorePatterns = await riStylesheetPatterns(root);
180
+ if (ignorePatterns.length > 0) {
181
+ patch.fmt = { ignorePatterns };
182
+ }
172
183
  if (!hasLocalPostCSSConfig(root)) {
173
- return {
174
- css: {
175
- postcss: {
176
- plugins: [postcss_default()]
177
- }
184
+ patch.css = {
185
+ postcss: {
186
+ plugins: [postcss_default()]
178
187
  }
179
188
  };
180
189
  }
181
- return {};
190
+ return patch;
182
191
  },
183
192
  configureServer(server) {
184
193
  root = server.config?.root ?? process.cwd();
194
+ enableSourceFileListCache();
195
+ server.watcher?.on("add", invalidateSourceFileListCache);
196
+ server.watcher?.on("unlink", invalidateSourceFileListCache);
197
+ server.watcher?.on("unlinkDir", invalidateSourceFileListCache);
198
+ server.httpServer?.once("close", invalidateSourceFileListCache);
185
199
  logger = {
186
200
  info: (msg) => server.config.logger?.info?.(msg, { timestamp: true }),
187
201
  warn: (msg) => server.config.logger?.warn?.(msg, { timestamp: true })
@@ -234,7 +248,8 @@ function rainbowindexVite() {
234
248
  }
235
249
  return null;
236
250
  },
237
- async handleHotUpdate({ file, server, modules }) {
251
+ async handleHotUpdate(ctx) {
252
+ const { file, server, modules } = ctx;
238
253
  if (++hotUpdateCount % PRUNE_INTERVAL === 0) {
239
254
  await pruneDeletedFiles();
240
255
  }
@@ -243,6 +258,18 @@ function rainbowindexVite() {
243
258
  return;
244
259
  }
245
260
  if (!isSourceFile(file)) return;
261
+ const previous = candidateSignatures.get(file);
262
+ let signature;
263
+ try {
264
+ const content = await ctx.read();
265
+ signature = [...extractClassesFromSource({ path: file, content })].sort(codepointCompare).join(" ");
266
+ } catch {
267
+ candidateSignatures.delete(file);
268
+ }
269
+ if (signature !== void 0) {
270
+ candidateSignatures.set(file, signature);
271
+ if (previous === signature) return;
272
+ }
246
273
  const extraModules = [];
247
274
  const hmrModules = new Set(modules);
248
275
  for (const cssFile of riCSSFiles) {
@@ -261,13 +288,14 @@ function rainbowindexVite() {
261
288
  }
262
289
  };
263
290
  async function pruneDeletedFiles() {
264
- const tracked = /* @__PURE__ */ new Set([...riCSSFiles, ...fileVersions.keys()]);
291
+ const tracked = /* @__PURE__ */ new Set([...riCSSFiles, ...fileVersions.keys(), ...candidateSignatures.keys()]);
265
292
  const checks = [...tracked].map(async (file) => {
266
293
  try {
267
294
  await access(file);
268
295
  } catch {
269
296
  riCSSFiles.delete(file);
270
297
  fileVersions.delete(file);
298
+ candidateSignatures.delete(file);
271
299
  }
272
300
  });
273
301
  await Promise.all(checks);
@@ -292,6 +320,19 @@ function rainbowindexVite() {
292
320
  }
293
321
  }
294
322
  }
323
+ async function riStylesheetPatterns(root2) {
324
+ const files = await findCSSFilesOnDisk(root2);
325
+ const active = await Promise.all(
326
+ files.map(async (file) => {
327
+ try {
328
+ return hasRIActivation(await readFile(file, "utf-8")) ? file : null;
329
+ } catch {
330
+ return null;
331
+ }
332
+ })
333
+ );
334
+ return active.filter((file) => file !== null).map((file) => relative(root2, file).replaceAll("\\", "/"));
335
+ }
295
336
  async function findCSSFilesOnDisk(root2) {
296
337
  const results = [];
297
338
  await collectCSSFiles(root2, results);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rainbowindex",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "CSS-first system for building and maintaining consistent user interfaces",
5
5
  "keywords": [
6
6
  "css",
@@ -16,6 +16,8 @@
16
16
  "postcss",
17
17
  "postcss-plugin",
18
18
  "vite-plugin",
19
+ "vite-plus",
20
+ "oxlint",
19
21
  "class-merge",
20
22
  "classnames",
21
23
  "tailwind",
@@ -65,6 +67,10 @@
65
67
  "types": "./dist/vite.d.ts",
66
68
  "default": "./dist/vite.mjs"
67
69
  },
70
+ "./oxlint": {
71
+ "types": "./dist/oxlint.d.ts",
72
+ "default": "./dist/oxlint.mjs"
73
+ },
68
74
  "./package.json": "./package.json"
69
75
  },
70
76
  "bin": {