rainbowindex 0.1.4 → 0.2.0

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,24 @@
1
+ // src/cli/optimize.ts
2
+ import { transform } from "lightningcss";
3
+ function optimizeCSS(css) {
4
+ try {
5
+ const result = transform({
6
+ filename: "output.css",
7
+ code: Buffer.from(css),
8
+ minify: true,
9
+ targets: {
10
+ chrome: 96 << 16,
11
+ firefox: 91 << 16,
12
+ safari: 15 << 16 | 4 << 8
13
+ }
14
+ });
15
+ return result.code.toString();
16
+ } catch (err) {
17
+ const msg = err instanceof Error ? err.message : String(err);
18
+ const sanitized = msg.replace(/\/[a-zA-Z][\w.-]*(?:\/[\w.-]+)+/g, "<path>").replace(/[A-Z]:\\[\w.-]+(?:\\[\w.-]+)+/gi, "<path>");
19
+ throw new Error(`CSS optimization failed: ${sanitized}`);
20
+ }
21
+ }
22
+ export {
23
+ optimizeCSS
24
+ };
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Generative color system — color domain model, OKLCH ramp generation,
3
+ * and light-dark() pairing.
4
+ *
5
+ * 2 numbers (chroma + hue) → 19-stop palette with automatic dark mode.
6
+ *
7
+ * The ramp is sampled from a fixed reference profile (`L_PROFILE` / `C_SHAPE` /
8
+ * `H_DRIFT`, captured from the reference palette): lightness is a curved,
9
+ * compressed scale (low suffix → light, high → dark), chroma is an asymmetric
10
+ * bell peaking at stop 500, and hue is near-flat. Dark mode is a simple ramp
11
+ * reversal — the dark value is the stop the ramp reaches at the mirror position
12
+ * (`1000 - suffix`), so stop 500 pivots to itself.
13
+ */
14
+ /** Per-color dark mode override strategy. */
15
+ type ColorDarkOverride = {
16
+ strategy: "mirror";
17
+ } | {
18
+ strategy: "fixed";
19
+ } | {
20
+ strategy: "shift";
21
+ chromaDelta: number;
22
+ hueDelta: number;
23
+ };
24
+ /**
25
+ * Color definition — discriminated union supporting:
26
+ * - Generative: `brand: 0.18 330;` → 19-stop palette with auto dark mode
27
+ * - Explicit: `accent: oklch(0.72 0.21 330);` → single color value
28
+ * - Pair: `surface: oklch(0.98 0.01 260) / oklch(0.15 0.01 260);` → light/dark pair
29
+ * - Alias: `theme: brand;` → references another color's palette via var()
30
+ */
31
+ type ColorDefinition = {
32
+ type: "generative";
33
+ chroma: number;
34
+ hue: number;
35
+ dark?: ColorDarkOverride;
36
+ inline?: boolean;
37
+ parabolic?: boolean;
38
+ chromaBoost?: boolean;
39
+ } | {
40
+ type: "explicit";
41
+ value: string;
42
+ } | {
43
+ type: "keyword";
44
+ value: string;
45
+ } | {
46
+ type: "pair";
47
+ light: string;
48
+ dark: string;
49
+ } | {
50
+ type: "alias";
51
+ source: string;
52
+ };
53
+ interface DarkModeConfig {
54
+ mode: "auto" | "off";
55
+ chromaBoost: number;
56
+ hueShift: number;
57
+ }
58
+
59
+ /**
60
+ * Default theme values — the static data that ships if the user writes no directives.
61
+ * `directives.ts` (Phase 5) parses user overrides and merges them with these defaults.
62
+ */
63
+
64
+ interface TextSize {
65
+ fontSize: string;
66
+ lineHeight: string;
67
+ }
68
+ type FluidUnit = "vw" | "vi" | "vmin" | "vmax";
69
+ interface FluidConfig {
70
+ min: string;
71
+ max: string;
72
+ unit?: FluidUnit;
73
+ multiplier?: number;
74
+ }
75
+ interface AnimationDefinition {
76
+ shorthand: string;
77
+ keyframes: string;
78
+ }
79
+ interface Theme {
80
+ spacing: {
81
+ base: string;
82
+ };
83
+ colors: Record<string, ColorDefinition>;
84
+ text: Record<string, TextSize>;
85
+ breakpoints: Record<string, string>;
86
+ rounded: Record<string, string>;
87
+ shadows: Record<string, string>;
88
+ weights: Record<string, number>;
89
+ easing: Record<string, string>;
90
+ fluid: FluidConfig;
91
+ animations: Record<string, AnimationDefinition>;
92
+ blur: Record<string, string>;
93
+ }
94
+ /**
95
+ * Keyword corner-shape values. `superellipse(N)` is represented separately
96
+ * as `{ superellipse: N }` since the numeric parameter isn't a keyword.
97
+ * Single source for the type, the scale table below, and the @rounded
98
+ * modifier parser (directives/parsers.ts).
99
+ */
100
+ declare const CORNER_SHAPE_KEYWORDS: readonly ["round", "scoop", "bevel", "notch", "square", "squircle"];
101
+ type CornerShapeKeyword = (typeof CORNER_SHAPE_KEYWORDS)[number];
102
+ type CornerShape = CornerShapeKeyword | {
103
+ superellipse: number;
104
+ };
105
+ declare const defaultTheme: Theme;
106
+
107
+ /**
108
+ * ri() — class merge function.
109
+ * Replaces both tailwind-merge and clsx.
110
+ *
111
+ * Re-exported from the package's main and browser entries as `ri()`.
112
+ * Right-to-left scan: rightmost class wins when two classes set the same CSS property.
113
+ *
114
+ * ## Concurrency
115
+ *
116
+ * The default `ri()` export uses module-level state published by
117
+ * `finalizeCompilationContext()`. This is safe for single-compilation
118
+ * environments (typical browser usage, single Vite build, PostCSS).
119
+ *
120
+ * In multi-tenant / SSR / concurrent-compilation environments, use
121
+ * `createRi(snapshot)` instead — it captures a frozen snapshot of the
122
+ * compilation state and uses its own independent cache:
123
+ *
124
+ * const snapshot = finalizeCompilationContext(ctx);
125
+ * const ri = createRi(snapshot);
126
+ */
127
+ type ClassInput = string | false | null | undefined | ClassInput[];
128
+ declare const DEFAULT_TEXT_SIZES: readonly ["xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl", "5xl"];
129
+ interface CompilationContext {
130
+ customStaticProps: Record<string, string[]>;
131
+ textSizes: Set<string>;
132
+ fontFamilies: Set<string>;
133
+ colorNames: Set<string>;
134
+ }
135
+ /**
136
+ * Frozen snapshot of compilation state for SSR-safe ri() instances.
137
+ * Created by finalizeCompilationContext() and consumed by createRi().
138
+ */
139
+ interface CompilationSnapshot {
140
+ readonly customStaticProps: Readonly<Record<string, string[]>>;
141
+ readonly textSizes: ReadonlySet<string>;
142
+ readonly fontFamilies: ReadonlySet<string>;
143
+ readonly colorNames: ReadonlySet<string>;
144
+ }
145
+ /**
146
+ * Merge class names with conflict resolution (replaces both tailwind-merge and clsx).
147
+ * Rightmost class wins when two classes set the same CSS property.
148
+ * Falsy values are filtered.
149
+ *
150
+ * @example
151
+ * ri('p-2 bg-red-500', 'p-4') // → 'bg-red-500 p-4'
152
+ * ri('px-2 py-1', 'p-4') // → 'p-4' (shorthand wins)
153
+ * ri('flex', isActive && 'bg-blue-500') // → 'flex bg-blue-500'
154
+ * ri('text-lg text-red-500') // → 'text-lg text-red-500' (different properties)
155
+ */
156
+ declare function ri(...inputs: ClassInput[]): string;
157
+ /**
158
+ * Create an isolated ri() instance bound to a specific compilation snapshot.
159
+ * Use this in SSR or multi-compilation environments where the global ri()
160
+ * would be corrupted by concurrent compilations.
161
+ *
162
+ * @example
163
+ * const snapshot = finalizeCompilationContext(ctx);
164
+ * const ri = createRi(snapshot);
165
+ * ri('p-2 bg-red-500', 'p-4') // → 'bg-red-500 p-4'
166
+ */
167
+ declare function createRi(snapshot?: CompilationSnapshot): (...inputs: ClassInput[]) => string;
168
+ /**
169
+ * Create a fresh compilation context.
170
+ * Called at the start of each compile() pass.
171
+ */
172
+ declare function createCompilationContext(): CompilationContext;
173
+ /**
174
+ * Register a custom utility's CSS properties in the compilation context.
175
+ * Called by the engine when processing @utility directives.
176
+ */
177
+ declare function registerCustomUtility(ctx: CompilationContext, name: string, properties: string[]): void;
178
+ /**
179
+ * Register custom text sizes so the merge function correctly classifies
180
+ * text-{custom} as a font-size utility rather than a color utility.
181
+ */
182
+ declare function registerCustomTextSizes(ctx: CompilationContext, sizes: string[]): void;
183
+ /**
184
+ * Register custom font family names so the merge function correctly classifies
185
+ * font-{custom} as a font-family utility rather than a font-weight utility.
186
+ */
187
+ declare function registerCustomFontFamilies(ctx: CompilationContext, families: string[]): void;
188
+ /**
189
+ * Register the resolved theme's color names so the merge function classifies
190
+ * bare flat colors (border-accent for `@color { accent: … }`) as color
191
+ * utilities rather than width/weight ones. Shaded forms (accent-500) already
192
+ * match the shade pattern and need no registration.
193
+ */
194
+ declare function registerColorNames(ctx: CompilationContext, names: string[]): void;
195
+ declare function finalizeCompilationContext(ctx: CompilationContext): CompilationSnapshot;
196
+
197
+ /**
198
+ * `safelist()` — declare utility classes that must be emitted regardless of
199
+ * whether the consumer's source files reference them directly.
200
+ *
201
+ * At runtime this is a plain identity-join: pass any number of strings (and
202
+ * falsy values, which are filtered) and receive a single space-joined string
203
+ * suitable for `className`. The function performs no global registration, has
204
+ * no side effects, and is tree-shake-safe.
205
+ *
206
+ * The build-time meaning comes from the scanner: when the source-file
207
+ * extractor encounters a `safelist(...)` call, it extracts every literal
208
+ * string argument as a class declaration — so the classes get emitted in the
209
+ * final CSS even though the consumer's source never names them literally.
210
+ *
211
+ * Primary use case is component libraries that ship classNames inside their
212
+ * bundled code (e.g. a curated icon set whose strokes are described by
213
+ * utility classes). The library wraps its declarations in `safelist(...)`,
214
+ * the consumer's setup points the scanner at the library's `dist/`, and the
215
+ * classes flow through unchanged. The Vite plugin auto-discovers libraries
216
+ * that opt in via a `rainbowindex.safelistSources` field in their
217
+ * `package.json`, so consumers typically don't have to add `@source` lines
218
+ * by hand.
219
+ *
220
+ * const ICON_BASE = safelist("stroke-cap-round", "stroke-join-round");
221
+ * const SidebarLeft = defineIcon({
222
+ * primitives: SIDEBAR,
223
+ * className: safelist(ICON_BASE, "-scale-x-100"),
224
+ * });
225
+ *
226
+ * Scanner contract:
227
+ * - Only STATIC string literals at the call site are extracted. Values
228
+ * passed through variables (`safelist(ICON_BASE, ...)`) won't be re-read
229
+ * at the outer call site, but the original `safelist("stroke-cap-round",
230
+ * ...)` that produced `ICON_BASE` is itself extracted, so the classes are
231
+ * still covered.
232
+ * - Template literals with no `${…}` interpolation are extracted; templates
233
+ * with interpolation are skipped.
234
+ * - Falsy arguments are dropped at runtime so conditional fragments compose
235
+ * naturally: `safelist("flex", side === "left" && "flex-row-reverse")`.
236
+ */
237
+ declare function safelist(...parts: ReadonlyArray<string | false | null | undefined>): string;
238
+
239
+ export { type AnimationDefinition as A, type ColorDefinition as C, DEFAULT_TEXT_SIZES as D, type FluidConfig as F, type TextSize as T, type CompilationContext as a, type CompilationSnapshot as b, type Theme as c, createCompilationContext as d, createRi as e, defaultTheme as f, finalizeCompilationContext as g, registerCustomFontFamilies as h, registerCustomTextSizes as i, registerCustomUtility as j, ri as k, type DarkModeConfig as l, type CornerShape as m, registerColorNames as r, safelist as s };
package/dist/vite.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ declare function rainbowindexVite(): Plugin;
4
+
5
+ export { rainbowindexVite as default };
package/dist/vite.mjs ADDED
@@ -0,0 +1,320 @@
1
+ import {
2
+ postcss_default
3
+ } from "./chunk-KCSNR2TV.mjs";
4
+ import {
5
+ expandApplyGroups,
6
+ findClosingBrace,
7
+ hasRIActivation,
8
+ isAtRuleBoundary,
9
+ isAtRuleNameChar,
10
+ isSourceFile
11
+ } from "./chunk-RPXZ3O6R.mjs";
12
+ import {
13
+ devWarn
14
+ } from "./chunk-5N4GPK26.mjs";
15
+
16
+ // src/integrations/vite.ts
17
+ import { existsSync } from "fs";
18
+ import { access, readFile, readdir } from "fs/promises";
19
+ import { join, relative, resolve } from "path";
20
+ var CSS_FILE_RE = /\.(?:module\.)?css$/;
21
+ var REMOVAL_BODY_DIRECTIVES = /* @__PURE__ */ new Set([
22
+ "color",
23
+ "text",
24
+ "spacing",
25
+ "breakpoint",
26
+ "rounded",
27
+ "shadow",
28
+ "weight",
29
+ "ease",
30
+ "blur",
31
+ "z",
32
+ "animate",
33
+ "leading",
34
+ "tracking",
35
+ "opacity",
36
+ "duration"
37
+ ]);
38
+ var KEYWORD_BODY_DIRECTIVES = /* @__PURE__ */ new Set(["fluid"]);
39
+ var REMOVAL_RE = /!([\w][\w-]*)\s*;/g;
40
+ var FLUID_KEYWORD_RE = /\b(no-parabolic|parabolic|no-shift|shift)\s*;?/g;
41
+ var COLOR_FLAG_RE = /(?<=[{;\s]|^)(inline|no-parabolic|parabolic)\s*(?:;|(?=}))/g;
42
+ function rewriteTopLevel(body, rewrite) {
43
+ if (!body.includes("{")) return rewrite(body);
44
+ let out = "";
45
+ let segStart = 0;
46
+ let depth = 0;
47
+ for (let i = 0; i < body.length; i++) {
48
+ const ch = body[i];
49
+ if (ch === "{") {
50
+ if (depth === 0) {
51
+ out += rewrite(body.slice(segStart, i));
52
+ segStart = i;
53
+ }
54
+ depth++;
55
+ } else if (ch === "}") {
56
+ if (depth > 0) depth--;
57
+ if (depth === 0) {
58
+ out += body.slice(segStart, i + 1);
59
+ segStart = i + 1;
60
+ }
61
+ }
62
+ }
63
+ out += depth === 0 ? rewrite(body.slice(segStart)) : body.slice(segStart);
64
+ return out;
65
+ }
66
+ function rewriteColorOptionFlag(_match, keyword) {
67
+ if (keyword === "inline") return "--ri-inline: true;";
68
+ const negated = keyword.startsWith("no-");
69
+ return `--ri-${negated ? keyword.slice(3) : keyword}: ${negated ? "false" : "true"};`;
70
+ }
71
+ function rewriteColorOptionFlags(body) {
72
+ if (!body.includes("{")) return body;
73
+ let out = "";
74
+ let segStart = 0;
75
+ let depth = 0;
76
+ let blockStart = -1;
77
+ for (let i = 0; i < body.length; i++) {
78
+ const ch = body[i];
79
+ if (ch === "{") {
80
+ if (depth === 0) {
81
+ out += body.slice(segStart, i + 1);
82
+ blockStart = i + 1;
83
+ }
84
+ depth++;
85
+ } else if (ch === "}") {
86
+ if (depth > 0) depth--;
87
+ if (depth === 0 && blockStart !== -1) {
88
+ out += body.slice(blockStart, i).replace(COLOR_FLAG_RE, rewriteColorOptionFlag);
89
+ out += "}";
90
+ segStart = i + 1;
91
+ blockStart = -1;
92
+ }
93
+ }
94
+ }
95
+ out += body.slice(segStart);
96
+ return out;
97
+ }
98
+ function rewriteDirectiveBodies(code) {
99
+ let out = "";
100
+ let last = 0;
101
+ let i = 0;
102
+ while (i < code.length) {
103
+ const at = code.indexOf("@", i);
104
+ if (at === -1) break;
105
+ if (!isAtRuleBoundary(code, at)) {
106
+ i = at + 1;
107
+ continue;
108
+ }
109
+ let nameEnd = at + 1;
110
+ while (nameEnd < code.length && isAtRuleNameChar(code.charCodeAt(nameEnd))) nameEnd++;
111
+ const name = code.slice(at + 1, nameEnd);
112
+ const removals = REMOVAL_BODY_DIRECTIVES.has(name);
113
+ const keywords = KEYWORD_BODY_DIRECTIVES.has(name);
114
+ if (!removals && !keywords) {
115
+ i = nameEnd;
116
+ continue;
117
+ }
118
+ let braceIdx = nameEnd;
119
+ while (braceIdx < code.length) {
120
+ const ch = code[braceIdx];
121
+ if (ch === "{" || ch === ";" || ch === "}") break;
122
+ braceIdx++;
123
+ }
124
+ if (code[braceIdx] !== "{") {
125
+ i = braceIdx + 1;
126
+ continue;
127
+ }
128
+ const close = findClosingBrace(code, braceIdx);
129
+ const bodyStart = braceIdx + 1;
130
+ const bodyEnd = close === -1 ? code.length : close;
131
+ let rewritten = rewriteTopLevel(code.slice(bodyStart, bodyEnd), (span) => {
132
+ let s = span;
133
+ if (removals) s = s.replace(REMOVAL_RE, "--ri-rm: $1;");
134
+ if (keywords) {
135
+ s = s.replace(FLUID_KEYWORD_RE, (_, kw) => {
136
+ const negated = kw.startsWith("no-");
137
+ return `--ri-${negated ? kw.slice(3) : kw}: ${negated ? "false" : "true"};`;
138
+ });
139
+ }
140
+ return s;
141
+ });
142
+ if (name === "color") rewritten = rewriteColorOptionFlags(rewritten);
143
+ out += code.slice(last, bodyStart) + rewritten;
144
+ last = bodyEnd;
145
+ i = bodyEnd;
146
+ }
147
+ if (last === 0) return code;
148
+ return out + code.slice(last);
149
+ }
150
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
151
+ var POSTCSS_CONFIG_FILES = [
152
+ "postcss.config.js",
153
+ "postcss.config.mjs",
154
+ "postcss.config.ts",
155
+ "postcss.config.cjs"
156
+ ];
157
+ function isIgnorableDirectoryReadError(err) {
158
+ return !!err && typeof err === "object" && "code" in err && (err.code === "ENOENT" || err.code === "ENOTDIR" || err.code === "EACCES" || err.code === "EPERM");
159
+ }
160
+ function hasLocalPostCSSConfig(root) {
161
+ return POSTCSS_CONFIG_FILES.some((name) => existsSync(resolve(root, name)));
162
+ }
163
+ function rainbowindexVite() {
164
+ let root = process.cwd();
165
+ let logger;
166
+ const riCSSFiles = /* @__PURE__ */ new Set();
167
+ const fileVersions = /* @__PURE__ */ new Map();
168
+ let hotUpdateCount = 0;
169
+ const PRUNE_INTERVAL = 50;
170
+ return {
171
+ name: "rainbowindex",
172
+ enforce: "pre",
173
+ async config(config) {
174
+ root = config.root ?? process.cwd();
175
+ if (!hasLocalPostCSSConfig(root)) {
176
+ return {
177
+ css: {
178
+ postcss: {
179
+ plugins: [postcss_default()]
180
+ }
181
+ }
182
+ };
183
+ }
184
+ return {};
185
+ },
186
+ configureServer(server) {
187
+ root = server.config?.root ?? process.cwd();
188
+ logger = {
189
+ info: (msg) => server.config.logger?.info?.(msg, { timestamp: true }),
190
+ warn: (msg) => server.config.logger?.warn?.(msg, { timestamp: true })
191
+ };
192
+ if (hasLocalPostCSSConfig(root)) {
193
+ logger.info("[rainbowindex] Using local PostCSS config \u2014 skipped auto-injection.");
194
+ } else {
195
+ logger.info("[rainbowindex] Injected PostCSS plugin (no local postcss.config.* found).");
196
+ }
197
+ server.httpServer?.once("listening", async () => {
198
+ const cssFiles = [];
199
+ const fileMap = server.moduleGraph.fileToModulesMap;
200
+ if (fileMap) {
201
+ for (const [file] of fileMap) {
202
+ if (CSS_FILE_RE.test(file)) {
203
+ cssFiles.push(file);
204
+ }
205
+ }
206
+ }
207
+ if (cssFiles.length === 0) {
208
+ const diskCSS = await findCSSFilesOnDisk(root);
209
+ cssFiles.push(...diskCSS);
210
+ }
211
+ await Promise.all(cssFiles.map((file) => checkCSSFileAsync(file)));
212
+ if (riCSSFiles.size === 0) {
213
+ logger?.warn(
214
+ `[RI-1602] rainbowindex Vite plugin is registered but no CSS entry with \`@import "rainbowindex"\` was found under ${root}. Create one (e.g. src/index.css) and import it from your app entry, then restart the dev server. Or run \`rainbowindex init\` to wire it up automatically.`
215
+ );
216
+ } else {
217
+ const list = [...riCSSFiles].map((f) => relative(root, f).replaceAll("\\", "/")).join(", ");
218
+ logger?.info(`[rainbowindex] CSS entries: ${list}`);
219
+ }
220
+ });
221
+ },
222
+ transform(code, id) {
223
+ const file = id.split("?")[0];
224
+ if (CSS_FILE_RE.test(file)) {
225
+ fileVersions.set(file, (fileVersions.get(file) ?? 0) + 1);
226
+ if (hasRIActivation(code)) {
227
+ riCSSFiles.add(file);
228
+ let safe = rewriteDirectiveBodies(code);
229
+ const expandWarnings = [];
230
+ safe = expandApplyGroups(safe, expandWarnings);
231
+ for (const w of expandWarnings) {
232
+ (logger?.warn ?? console.warn)(`[rainbowindex] ${w}`);
233
+ }
234
+ return safe !== code ? safe : null;
235
+ }
236
+ riCSSFiles.delete(file);
237
+ }
238
+ return null;
239
+ },
240
+ async handleHotUpdate({ file, server, modules }) {
241
+ if (++hotUpdateCount % PRUNE_INTERVAL === 0) {
242
+ await pruneDeletedFiles();
243
+ }
244
+ if (CSS_FILE_RE.test(file)) {
245
+ await checkCSSFileAsync(file);
246
+ return;
247
+ }
248
+ if (!isSourceFile(file)) return;
249
+ const extraModules = [];
250
+ for (const cssFile of riCSSFiles) {
251
+ const mods = server.moduleGraph.getModulesByFile(cssFile);
252
+ if (mods) {
253
+ for (const mod of mods) {
254
+ if (!modules.includes(mod)) {
255
+ extraModules.push(mod);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ if (extraModules.length > 0) {
261
+ return [...modules, ...extraModules];
262
+ }
263
+ }
264
+ };
265
+ async function pruneDeletedFiles() {
266
+ const tracked = /* @__PURE__ */ new Set([...riCSSFiles, ...fileVersions.keys()]);
267
+ const checks = [...tracked].map(async (file) => {
268
+ try {
269
+ await access(file);
270
+ } catch {
271
+ riCSSFiles.delete(file);
272
+ fileVersions.delete(file);
273
+ }
274
+ });
275
+ await Promise.all(checks);
276
+ }
277
+ async function collectCSSFiles(dir, results) {
278
+ let entries;
279
+ try {
280
+ entries = await readdir(dir, { withFileTypes: true });
281
+ } catch (err) {
282
+ if (!isIgnorableDirectoryReadError(err)) {
283
+ const msg = err instanceof Error ? err.message : String(err);
284
+ devWarn(`[RI-1601] Failed to scan CSS files in "${dir}": ${msg}`);
285
+ }
286
+ return;
287
+ }
288
+ for (const entry of entries) {
289
+ if (entry.isDirectory()) {
290
+ if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
291
+ await collectCSSFiles(join(dir, entry.name), results);
292
+ } else if (entry.isFile() && CSS_FILE_RE.test(entry.name)) {
293
+ results.push(join(dir, entry.name));
294
+ }
295
+ }
296
+ }
297
+ async function findCSSFilesOnDisk(root2) {
298
+ const results = [];
299
+ await collectCSSFiles(root2, results);
300
+ return results;
301
+ }
302
+ async function checkCSSFileAsync(file) {
303
+ const versionBefore = fileVersions.get(file) ?? 0;
304
+ try {
305
+ const raw = await readFile(file, "utf-8");
306
+ if ((fileVersions.get(file) ?? 0) !== versionBefore) return;
307
+ if (hasRIActivation(raw)) {
308
+ riCSSFiles.add(file);
309
+ } else {
310
+ riCSSFiles.delete(file);
311
+ }
312
+ } catch (_err) {
313
+ if ((fileVersions.get(file) ?? 0) !== versionBefore) return;
314
+ riCSSFiles.delete(file);
315
+ }
316
+ }
317
+ }
318
+ export {
319
+ rainbowindexVite as default
320
+ };