rainbowindex 0.4.1 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,77 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.0] - 2026-08-27
9
+
10
+ ### Added
11
+
12
+ - **`rainbowindex scan <file|glob…>`** — prints every class candidate the
13
+ scanner extracts from each file, with scanner warnings on stderr. Answers
14
+ "why doesn't my class generate?" directly: a class missing from the list
15
+ was never seen by the scanner (check the markup), while a class listed
16
+ there that still produces no rule fails later (check the build warnings).
17
+ - **RI-1411 — skipped over-long lines are now reported.** The scanner drops
18
+ lines above the minified-input guard, previously in silence. It now warns
19
+ once per file with the path and line count. Suppressed for `node_modules`
20
+ paths, where minified dists are the guard's intended target.
21
+ - **RI-1038 — uppercase `@utility` names warn.** The markup scanner only
22
+ matches lowercase tokens, so `@utility cardHeader` could never trigger
23
+ from `class="cardHeader"`. The utility still works through `@a`/`@apply`
24
+ and inline `@source`, so it is kept — but no longer silently unreachable.
25
+
26
+ ### Changed
27
+
28
+ - **Fallback stacks moved from preflight into `@font`.** The
29
+ `--sans-fallback` / `--serif-fallback` / `--mono-fallback` variables were
30
+ emitted into every project and referenced by nothing. A manual `@font`
31
+ slot that declares no fallbacks now gets the default system stack appended
32
+ to its `--font-<slot>` value instead (`sans: "Chartwell";` →
33
+ `"Chartwell", ui-sans-serif, system-ui, …`); a slot that declares its own
34
+ fallbacks is untouched. Font tokens no longer depend on a preflight
35
+ category that users can switch off.
36
+ - **Focus ring uses literal lengths.** `:focus-visible` drew its outline at
37
+ `var(--spacing)` (4px by default) and offset at half that, so changing the
38
+ spacing scale silently resized every focus ring. Now a flat 2px width and
39
+ 2px offset.
40
+ - **Placeholder color follows the text color** —
41
+ `color-mix(in oklab, currentColor 48%, transparent)` instead of a
42
+ hardcoded gray that ignored the theme in dark mode.
43
+ - **Scanner line-length guard raised from 2,000 to 10,000 characters.** Real
44
+ minified files run far above this, while hand-written long lines (inline
45
+ SVG path data, long attribute stacks) sit below it. `MAX_LINE_LENGTH` is
46
+ exported so tooling and tests derive from it instead of hardcoding.
47
+ - **Default source patterns scan every root HTML file** (`*.html`, not just
48
+ `index.html`), so Vite multi-page apps are covered without an explicit
49
+ `@source`. `dist`, `build`, and `public` remain excluded.
50
+
51
+ ### Removed
52
+
53
+ - **Preflight `select-reset`.** It styled rather than reset, and shipped
54
+ three defects: its chevron was a `currentColor` SVG in a `background-image`,
55
+ which resolves to black and disappears on dark backgrounds; the bare
56
+ `select` selector also hit `<select multiple>`, giving listboxes a floating
57
+ chevron and 2.5rem of padding; and `appearance: none` stripped the native
58
+ control on every platform. Style selects in your own CSS.
59
+ - **Preflight `:focus:not(:focus-visible) { outline: none }`** — no current
60
+ browser draws an outline for plain `:focus`, so the rule was dead.
61
+
62
+ ### Fixed
63
+
64
+ - **Quoted class attributes on over-long lines are no longer lost.** A
65
+ `className="…"` sharing a line with multi-KB inline SVG path data
66
+ generated nothing: the whole-file scan skipped the line for length, and
67
+ the attribute collector stripped the quotes and then searched only for
68
+ string literals *nested inside* the value, of which a plain quoted
69
+ attribute has none. Quoted attribute values are now tokenized directly, in
70
+ the one shared collector — so JSX, HTML, Vue, Svelte, and object-literal
71
+ (`{ className: "…" }`) syntax are all fixed together. Non-quoted
72
+ expression values keep their existing semantics.
73
+ - **List margins are reset.** `list-reset` removed bullets and padding while
74
+ `ol`, `ul`, and `menu` kept the browser's `margin-block: 1em`, leaving
75
+ unexplained gaps around navigation and menus.
76
+ - **`fieldset` and `legend` are reset** — their default margin and padding
77
+ survived while every other form control was flush.
78
+
8
79
  ## [0.4.1] - 2026-08-25
9
80
 
10
81
  ### Added
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  compileScannedProject
3
- } from "./chunk-53WD6U2X.mjs";
3
+ } from "./chunk-DT5HYIM3.mjs";
4
4
  import {
5
5
  APPLY_ALIASES,
6
6
  DIRECTIVE_NAMES_SET,
@@ -20,7 +20,7 @@ import {
20
20
  resolveUtilityDeclarations,
21
21
  resolveVariant,
22
22
  splitSelectorList
23
- } from "./chunk-6GTG5SZJ.mjs";
23
+ } from "./chunk-ZI5ZYNSU.mjs";
24
24
 
25
25
  // src/integrations/postcss/index.ts
26
26
  import postcss2 from "postcss";
@@ -19,7 +19,7 @@ import {
19
19
  renderCSS,
20
20
  scanCSSForTokenUsage,
21
21
  withTimeout
22
- } from "./chunk-6GTG5SZJ.mjs";
22
+ } from "./chunk-ZI5ZYNSU.mjs";
23
23
  import {
24
24
  checkPaletteContrast,
25
25
  generateAllColorVariables,
@@ -629,7 +629,8 @@ function generateFontCSS(slot) {
629
629
  return { imports, fontFaces, variables, warnings };
630
630
  }
631
631
  if (slot.kind === "manual") {
632
- const stack = [`"${escapeFontFamily(slot.family)}"`, ...slot.fallback].join(", ");
632
+ const fallback = slot.fallback.length > 0 ? slot.fallback.join(", ") : getFallbackStack(slot.slot);
633
+ const stack = [`"${escapeFontFamily(slot.family)}"`, fallback].join(", ");
633
634
  variables.push(`--font-${slot.slot}: ${stack};`);
634
635
  pushFeatureVars();
635
636
  return { imports, fontFaces, variables, warnings };
@@ -813,7 +814,7 @@ function errMessage(err) {
813
814
 
814
815
  // src/scanner/sources.ts
815
816
  var DEFAULT_PATTERNS = Object.freeze([
816
- "index.html",
817
+ "*.html",
817
818
  "src/**/*.{html,js,jsx,ts,tsx,mdx,vue,svelte}"
818
819
  ]);
819
820
  var DEFAULT_EXCLUDES = Object.freeze([
@@ -1209,7 +1210,7 @@ var modules = [
1209
1210
  {
1210
1211
  name: "margins",
1211
1212
  category: "core",
1212
- css: `body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, pre {
1213
+ css: `body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, pre, ol, ul, menu {
1213
1214
  margin: 0;
1214
1215
  }`
1215
1216
  },
@@ -1226,9 +1227,6 @@ var modules = [
1226
1227
  name: "root-defaults",
1227
1228
  category: "core",
1228
1229
  css: `:root {
1229
- --sans-fallback: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
1230
- --serif-fallback: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
1231
- --mono-fallback: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
1232
1230
  line-height: 1.5;
1233
1231
  -webkit-text-size-adjust: 100%;
1234
1232
  tab-size: 4;
@@ -1337,7 +1335,7 @@ button {
1337
1335
  category: "forms",
1338
1336
  css: `input::placeholder, textarea::placeholder {
1339
1337
  opacity: 1;
1340
- color: oklch(0.556 0 0);
1338
+ color: color-mix(in oklab, currentColor 48%, transparent);
1341
1339
  }
1342
1340
  input:where([type="button"], [type="reset"], [type="submit"]) {
1343
1341
  -webkit-appearance: button;
@@ -1352,16 +1350,14 @@ input:where([type="button"], [type="reset"], [type="submit"]) {
1352
1350
  }`
1353
1351
  },
1354
1352
  {
1355
- name: "select-reset",
1353
+ name: "fieldset-reset",
1356
1354
  category: "forms",
1357
- css: `select {
1358
- -webkit-appearance: none;
1359
- appearance: none;
1360
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor'%3e%3cpath fill-rule='evenodd' d='M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06z' clip-rule='evenodd'/%3e%3c/svg%3e");
1361
- background-position: right 0.5rem center;
1362
- background-repeat: no-repeat;
1363
- background-size: 1.5em 1.5em;
1364
- padding-inline-end: 2.5rem;
1355
+ css: `fieldset {
1356
+ margin: 0;
1357
+ padding: 0;
1358
+ }
1359
+ legend {
1360
+ padding: 0;
1365
1361
  }`
1366
1362
  },
1367
1363
  // ── Interactive ──────────────────────────────────────────
@@ -1369,13 +1365,10 @@ input:where([type="button"], [type="reset"], [type="submit"]) {
1369
1365
  name: "focus-visible",
1370
1366
  category: "interactive",
1371
1367
  css: `:focus-visible {
1372
- outline-width: var(--spacing);
1368
+ outline-width: 2px;
1373
1369
  outline-style: solid;
1374
- outline-offset: calc(var(--spacing) * 0.5);
1370
+ outline-offset: 2px;
1375
1371
  outline-color: currentColor;
1376
- }
1377
- :focus:not(:focus-visible) {
1378
- outline: none;
1379
1372
  }`
1380
1373
  },
1381
1374
  {
@@ -15,7 +15,7 @@ import {
15
15
  codepointCompare,
16
16
  parseUtility,
17
17
  resolveUtilityDeclarations
18
- } from "./chunk-6GTG5SZJ.mjs";
18
+ } from "./chunk-ZI5ZYNSU.mjs";
19
19
  import {
20
20
  SPECIAL_COLORS
21
21
  } from "./chunk-KRZL4IDK.mjs";
@@ -1319,7 +1319,14 @@ function stripLeadingClassDot(name) {
1319
1319
  return name.startsWith(".") ? name.slice(1) : name;
1320
1320
  }
1321
1321
  function isValidUtilityName(name, warnings) {
1322
- if (IDENT_KEY_RE.test(name)) return true;
1322
+ if (IDENT_KEY_RE.test(name)) {
1323
+ if (/[A-Z]/.test(name)) {
1324
+ warnings?.push(
1325
+ `[RI-1038] @utility name "${name}" contains uppercase letters \u2014 the markup scanner only matches lowercase tokens, so class="${name}" will never generate it. It still works via @a/@apply and inline @source. Prefer a lowercase-hyphen name.`
1326
+ );
1327
+ }
1328
+ return true;
1329
+ }
1323
1330
  warnings?.push(
1324
1331
  `[RI-1035] Invalid @utility name "${name}" \u2014 names may only contain letters, numbers, hyphens, and underscores (plus an optional trailing "-*" for functional utilities). The utility was skipped.`
1325
1332
  );
@@ -9205,7 +9212,8 @@ function readAssignedValue(source, start) {
9205
9212
  return {
9206
9213
  value: source.slice(i + 1, end2),
9207
9214
  end: end2,
9208
- valueStart: i + 1
9215
+ valueStart: i + 1,
9216
+ quoted: true
9209
9217
  };
9210
9218
  }
9211
9219
  if (ch === "`") {
@@ -9213,7 +9221,8 @@ function readAssignedValue(source, start) {
9213
9221
  return {
9214
9222
  value: source.slice(i + 1, end2),
9215
9223
  end: end2,
9216
- valueStart: i + 1
9224
+ valueStart: i + 1,
9225
+ quoted: false
9217
9226
  };
9218
9227
  }
9219
9228
  if (ch in BRACKET_PAIRS) {
@@ -9222,7 +9231,8 @@ function readAssignedValue(source, start) {
9222
9231
  return {
9223
9232
  value: source.slice(i + 1, end2),
9224
9233
  end: end2,
9225
- valueStart: i + 1
9234
+ valueStart: i + 1,
9235
+ quoted: false
9226
9236
  };
9227
9237
  }
9228
9238
  let end = i;
@@ -9230,7 +9240,8 @@ function readAssignedValue(source, start) {
9230
9240
  return {
9231
9241
  value: source.slice(i, end),
9232
9242
  end: end - 1,
9233
- valueStart: i
9243
+ valueStart: i,
9244
+ quoted: false
9234
9245
  };
9235
9246
  }
9236
9247
  function splitTopLevelArgs(source) {
@@ -9425,7 +9436,7 @@ var ARBITRARY_PROPERTY = /\[[a-z-][^\]]*:[^\]]+\](?:\/(?:[\w.]+|\[[^\]]*\]|\([^)
9425
9436
  var CLASS_RE_SOURCE = `${BOUNDARY}(${NEGATIVE}${VARIANT_PREFIX}(?:${UTILITY_VALUE}|${VARIANT_GROUP}|${ARBITRARY_PROPERTY})${IMPORTANT_SUFFIX})`;
9426
9437
  var CLASS_RE = new RegExp(CLASS_RE_SOURCE, "g");
9427
9438
  var VARIANT_STRIP_RE = new RegExp(`^(?:${VARIANT_SEGMENT})+`);
9428
- var MAX_LINE_LENGTH = 2e3;
9439
+ var MAX_LINE_LENGTH = 1e4;
9429
9440
  var CLASS_HELPERS = [
9430
9441
  "clsx",
9431
9442
  "cn",
@@ -9628,12 +9639,11 @@ function collectAssignedValues(sink, source, regex, visitor = scanClassTokens, b
9628
9639
  const value = raw.trim();
9629
9640
  if (!value) continue;
9630
9641
  sink.markContext?.(base + parsed.valueStart, base + parsed.valueStart + raw.length);
9631
- visitor(
9632
- sink,
9633
- value,
9634
- base + parsed.valueStart + (raw.length - raw.trimStart().length),
9635
- warnings
9636
- );
9642
+ const valueOffset = base + parsed.valueStart + (raw.length - raw.trimStart().length);
9643
+ if (parsed.quoted && visitor !== scanClassTokens) {
9644
+ scanClassTokens(sink, value, valueOffset, warnings);
9645
+ }
9646
+ visitor(sink, value, valueOffset, warnings);
9637
9647
  regex.lastIndex = Math.max(regex.lastIndex, parsed.end + 1);
9638
9648
  }
9639
9649
  }
@@ -9807,10 +9817,8 @@ var NOOP_VISITOR = () => void 0;
9807
9817
  function extractHTML(sink, context, warnings) {
9808
9818
  sink.setOrigin?.("plain");
9809
9819
  scanClassTokens(sink, context.content, 0, warnings);
9810
- if (sink.wantsPositions) {
9811
- sink.setOrigin?.("attribute");
9812
- collectAssignedValues(sink, context.content, /\bclass\s*=/g, NOOP_VISITOR, 0, warnings);
9813
- }
9820
+ sink.setOrigin?.("attribute");
9821
+ collectAssignedValues(sink, context.content, /\bclass\s*=/g, NOOP_VISITOR, 0, warnings);
9814
9822
  }
9815
9823
  function extractJSXTSX(sink, context, warnings) {
9816
9824
  const content = context.content;
@@ -9854,16 +9862,7 @@ function extractVue(sink, context, warnings) {
9854
9862
  0,
9855
9863
  warnings
9856
9864
  );
9857
- if (sink.wantsPositions) {
9858
- collectAssignedValues(
9859
- sink,
9860
- context.content,
9861
- /(?<![:\w-])class\s*=/g,
9862
- NOOP_VISITOR,
9863
- 0,
9864
- warnings
9865
- );
9866
- }
9865
+ collectAssignedValues(sink, context.content, /(?<![:\w-])class\s*=/g, NOOP_VISITOR, 0, warnings);
9867
9866
  }
9868
9867
  function extractSvelte(sink, context, warnings) {
9869
9868
  sink.setOrigin?.("plain");
@@ -9897,7 +9896,26 @@ var EXTRACTORS = [
9897
9896
  extract: extractJSXTSX
9898
9897
  }
9899
9898
  ];
9899
+ function warnOverLongLines(input, warnings) {
9900
+ const content = input.content;
9901
+ if (!warnings || content.length <= MAX_LINE_LENGTH) return;
9902
+ if (input.path?.includes("node_modules")) return;
9903
+ let count = 0;
9904
+ let start = 0;
9905
+ for (; ; ) {
9906
+ const idx = content.indexOf("\n", start);
9907
+ const end = idx === -1 ? content.length : idx;
9908
+ if (end - start > MAX_LINE_LENGTH) count++;
9909
+ if (idx === -1) break;
9910
+ start = idx + 1;
9911
+ }
9912
+ if (count === 0) return;
9913
+ warnings.push(
9914
+ `[RI-1411] ${input.path ?? "<source>"}: ${count} line(s) longer than ${MAX_LINE_LENGTH} characters were skipped by the class scanner (minified-input guard). Quoted class attributes on those lines are still read; other class references there are not \u2014 split the long lines.`
9915
+ );
9916
+ }
9900
9917
  function extractInto(sink, input, warnings) {
9918
+ warnOverLongLines(input, warnings);
9901
9919
  let handled = false;
9902
9920
  for (const extractor of EXTRACTORS) {
9903
9921
  if (extractor.test(input)) {
package/dist/cli.mjs CHANGED
@@ -2,28 +2,30 @@
2
2
  import {
3
3
  CSS_ENTRY_CANDIDATES,
4
4
  enumerateClassNames
5
- } from "./chunk-WYMCN5OC.mjs";
5
+ } from "./chunk-NXJZX6KI.mjs";
6
6
  import {
7
7
  DEFAULT_EXCLUDES,
8
8
  DEFAULT_PATTERNS,
9
9
  compileScannedProject,
10
10
  getFontPreloadLinks
11
- } from "./chunk-53WD6U2X.mjs";
11
+ } from "./chunk-DT5HYIM3.mjs";
12
12
  import {
13
13
  MAX_DIRECTIVE_INPUT_SIZE,
14
+ codepointCompare,
15
+ extractClassesFromSource,
14
16
  extractDirectives,
15
17
  hasApplyLikeDirective,
16
18
  hasRIActivation,
17
19
  listVariants,
18
20
  resolveDirectives
19
- } from "./chunk-6GTG5SZJ.mjs";
21
+ } from "./chunk-ZI5ZYNSU.mjs";
20
22
  import {
21
23
  devWarn
22
24
  } from "./chunk-KRZL4IDK.mjs";
23
25
 
24
26
  // src/entries/cli.ts
25
27
  import { realpathSync } from "fs";
26
- import { resolve as resolve6 } from "path";
28
+ import { resolve as resolve7 } from "path";
27
29
  import { pathToFileURL } from "url";
28
30
 
29
31
  // src/cli/args.ts
@@ -153,6 +155,22 @@ Examples:
153
155
  rainbowindex generate-types
154
156
  rainbowindex generate-types --strict --css src/styles.css`
155
157
  },
158
+ scan: {
159
+ summary: "Show what the class scanner extracts (debugging)",
160
+ usage: " rainbowindex scan <file|glob...>",
161
+ flags: [],
162
+ positionals: "globs",
163
+ body: `Output:
164
+ One line per extracted class candidate, per file. Scanner warnings (skipped
165
+ over-long lines, unreadable files) print to stderr with [RI-NNNN] codes.
166
+ A class missing here was never seen by the scanner \u2014 check how it appears
167
+ in the markup. A class listed here that still does not generate fails
168
+ later \u2014 check the build warnings.
169
+
170
+ Examples:
171
+ rainbowindex scan src/components/icons/logo.tsx
172
+ rainbowindex scan "src/**/*.tsx"`
173
+ },
156
174
  "preload-fonts": {
157
175
  summary: 'Generate <link rel="preload"> tags',
158
176
  usage: " rainbowindex preload-fonts [options]",
@@ -261,6 +279,11 @@ function parseArgs(argv, callbacks) {
261
279
  if (opts.command === "create" && !opts.targetDir) {
262
280
  throw new Error("create requires a project directory. Example: rainbowindex create my-app");
263
281
  }
282
+ if (opts.command === "scan" && opts.globs.length === 0) {
283
+ throw new Error(
284
+ 'scan requires at least one file or glob. Example: rainbowindex scan "src/**/*.tsx"'
285
+ );
286
+ }
264
287
  if (opts.watch && !opts.output) {
265
288
  throw new Error(
266
289
  '--output is required with --watch. Example: rainbowindex "src/**/*.tsx" --watch -o dist/styles.css'
@@ -303,8 +326,7 @@ function printHelp(subcommand) {
303
326
  ${spec.usage}`,
304
327
  ...spec.intro ? [spec.intro] : [],
305
328
  `Options:
306
- ${renderFlagLines(spec.flags)}
307
- ${HELP_LINE}`,
329
+ ${[renderFlagLines(spec.flags), HELP_LINE].filter(Boolean).join("\n")}`,
308
330
  spec.body
309
331
  ];
310
332
  console.log(`
@@ -321,6 +343,7 @@ Usage:
321
343
  rainbowindex create <dir> Scaffold a Vite app with Rainbow Index ready
322
344
  rainbowindex generate-types Generate TypeScript types for ri()
323
345
  rainbowindex preload-fonts Generate font preload link tags
346
+ rainbowindex scan <file...> Show what the class scanner extracts
324
347
 
325
348
  Run \`rainbowindex <subcommand> --help\` for subcommand-specific options.
326
349
 
@@ -772,11 +795,40 @@ async function watchMode(opts, cwd) {
772
795
  process.on("SIGTERM", cleanup);
773
796
  }
774
797
 
798
+ // src/cli/scan.ts
799
+ import { readFile as readFile3 } from "fs/promises";
800
+ import { relative as relative2, resolve as resolve5 } from "path";
801
+ import { glob } from "tinyglobby";
802
+ async function scanFiles(opts, cwd) {
803
+ const files = [...new Set(await glob(opts.globs, { cwd }))].sort(codepointCompare);
804
+ if (files.length === 0) {
805
+ throw new Error(`No files matched ${opts.globs.map((g) => `"${g}"`).join(", ")}.`);
806
+ }
807
+ for (const file of files) {
808
+ const path = resolve5(cwd, file);
809
+ const warnings = [];
810
+ const classes = extractClassesFromSource(
811
+ { path, content: await readFile3(path, "utf-8") },
812
+ warnings
813
+ );
814
+ const sorted = [...classes].sort(codepointCompare);
815
+ console.log(
816
+ `${relative2(cwd, path)} (${sorted.length} class${sorted.length === 1 ? "" : "es"})`
817
+ );
818
+ for (const cls of sorted) {
819
+ console.log(` ${cls}`);
820
+ }
821
+ for (const warning of warnings) {
822
+ console.error(` ${warning}`);
823
+ }
824
+ }
825
+ }
826
+
775
827
  // src/cli/vite-setup.ts
776
828
  import { spawn } from "child_process";
777
829
  import { existsSync as existsSync3, readFileSync } from "fs";
778
- import { mkdir as mkdir2, readFile as readFile3, readdir, writeFile as writeFile3 } from "fs/promises";
779
- import { dirname as dirname3, relative as relative2, resolve as resolve5 } from "path";
830
+ import { mkdir as mkdir2, readFile as readFile4, readdir, writeFile as writeFile3 } from "fs/promises";
831
+ import { dirname as dirname3, relative as relative3, resolve as resolve6 } from "path";
780
832
  var VITE_CONFIG_FILES = [
781
833
  "vite.config.ts",
782
834
  "vite.config.mts",
@@ -839,32 +891,32 @@ async function initViteProject(opts, cwd, deps = {}) {
839
891
  ensureStylesheet(cssPath)
840
892
  ]);
841
893
  const entryChanged = cssCreated ? await ensureStylesheetImport(entryFile, cssPath) : false;
842
- const rootLabel = relative2(process.cwd(), cwd) || ".";
894
+ const rootLabel = relative3(process.cwd(), cwd) || ".";
843
895
  console.log(`[rainbowindex] Initialized Vite project: ${rootLabel}`);
844
896
  if (dependencyInstalled) {
845
897
  console.log("[rainbowindex] Added dev dependency: rainbowindex");
846
898
  }
847
899
  if (configCreated) {
848
900
  console.log(
849
- `[rainbowindex] Created Vite config: ${relative2(cwd, configPath).replaceAll("\\", "/")}`
901
+ `[rainbowindex] Created Vite config: ${relative3(cwd, configPath).replaceAll("\\", "/")}`
850
902
  );
851
903
  } else if (configChanged) {
852
904
  console.log(
853
- `[rainbowindex] Updated Vite config: ${relative2(cwd, configPath).replaceAll("\\", "/")}`
905
+ `[rainbowindex] Updated Vite config: ${relative3(cwd, configPath).replaceAll("\\", "/")}`
854
906
  );
855
907
  }
856
908
  if (cssCreated) {
857
909
  console.log(
858
- `[rainbowindex] Created stylesheet: ${relative2(cwd, cssPath).replaceAll("\\", "/")}`
910
+ `[rainbowindex] Created stylesheet: ${relative3(cwd, cssPath).replaceAll("\\", "/")}`
859
911
  );
860
912
  } else if (cssChanged) {
861
913
  console.log(
862
- `[rainbowindex] Updated stylesheet: ${relative2(cwd, cssPath).replaceAll("\\", "/")}`
914
+ `[rainbowindex] Updated stylesheet: ${relative3(cwd, cssPath).replaceAll("\\", "/")}`
863
915
  );
864
916
  }
865
917
  if (entryChanged && entryFile) {
866
918
  console.log(
867
- `[rainbowindex] Added stylesheet import: ${relative2(cwd, entryFile).replaceAll("\\", "/")}`
919
+ `[rainbowindex] Added stylesheet import: ${relative3(cwd, entryFile).replaceAll("\\", "/")}`
868
920
  );
869
921
  }
870
922
  if (!dependencyInstalled && !configChanged && !configCreated && !cssChanged && !cssCreated) {
@@ -888,7 +940,7 @@ async function createViteProject(opts, cwd, deps = {}) {
888
940
  const packageManager = deps.packageManager ?? detectPackageManager(cwd, deps.env);
889
941
  const runner = deps.runner ?? DEFAULT_RUNNER;
890
942
  const targetDir = opts.targetDir;
891
- const targetRoot = resolve5(cwd, targetDir);
943
+ const targetRoot = resolve6(cwd, targetDir);
892
944
  await ensureScaffoldTargetIsUsable(targetRoot);
893
945
  const template = opts.template || DEFAULT_CREATE_TEMPLATE;
894
946
  const scaffold = getCreateCommand(packageManager, targetDir, template);
@@ -899,15 +951,15 @@ async function createViteProject(opts, cwd, deps = {}) {
899
951
  packageManager,
900
952
  runner
901
953
  });
902
- const displayTarget = relative2(cwd, targetRoot) || ".";
954
+ const displayTarget = relative3(cwd, targetRoot) || ".";
903
955
  console.log(`[rainbowindex] Ready: ${displayTarget}`);
904
956
  console.log(`[rainbowindex] Next: cd ${displayTarget} && ${packageManager} run dev`);
905
957
  }
906
958
  function detectPackageManager(cwd, env = process.env) {
907
- if (existsSync3(resolve5(cwd, "pnpm-lock.yaml"))) return "pnpm";
908
- if (existsSync3(resolve5(cwd, "yarn.lock"))) return "yarn";
909
- if (existsSync3(resolve5(cwd, "bun.lock")) || existsSync3(resolve5(cwd, "bun.lockb"))) return "bun";
910
- if (existsSync3(resolve5(cwd, "package-lock.json")) || existsSync3(resolve5(cwd, "npm-shrinkwrap.json"))) {
959
+ if (existsSync3(resolve6(cwd, "pnpm-lock.yaml"))) return "pnpm";
960
+ if (existsSync3(resolve6(cwd, "yarn.lock"))) return "yarn";
961
+ if (existsSync3(resolve6(cwd, "bun.lock")) || existsSync3(resolve6(cwd, "bun.lockb"))) return "bun";
962
+ if (existsSync3(resolve6(cwd, "package-lock.json")) || existsSync3(resolve6(cwd, "npm-shrinkwrap.json"))) {
911
963
  return "npm";
912
964
  }
913
965
  const packageJSON = readPackageJSONSync(cwd);
@@ -952,12 +1004,12 @@ async function installRainbowIndex(cwd, packageManager, runner) {
952
1004
  async function ensureViteConfig(cwd, packageJSON, existingPath) {
953
1005
  if (!existingPath) {
954
1006
  const fileName = chooseViteConfigName(cwd, packageJSON);
955
- const configPath = resolve5(cwd, fileName);
1007
+ const configPath = resolve6(cwd, fileName);
956
1008
  const content = 'import { defineConfig } from "vite";\nimport rainbowindex from "rainbowindex/vite";\n\nexport default defineConfig({\n plugins: [rainbowindex()],\n});\n';
957
1009
  await writeFile3(configPath, content, "utf-8");
958
1010
  return { path: configPath, changed: true, created: true };
959
1011
  }
960
- const original = await readFile3(existingPath, "utf-8");
1012
+ const original = await readFile4(existingPath, "utf-8");
961
1013
  const updated = patchViteConfig(original, existingPath);
962
1014
  if (updated === original) {
963
1015
  return { path: existingPath, changed: false, created: false };
@@ -1086,14 +1138,14 @@ function findMatchingDelimiter(input, start, open, close) {
1086
1138
  return -1;
1087
1139
  }
1088
1140
  async function resolveStylesheetPath(cwd, cssFile) {
1089
- if (cssFile) return resolve5(cwd, cssFile);
1141
+ if (cssFile) return resolve6(cwd, cssFile);
1090
1142
  const importedCSS = await findImportedStylesheet(cwd);
1091
1143
  if (importedCSS) return importedCSS;
1092
1144
  for (const candidate of CSS_ENTRY_CANDIDATES) {
1093
- const fullPath = resolve5(cwd, candidate);
1145
+ const fullPath = resolve6(cwd, candidate);
1094
1146
  if (existsSync3(fullPath)) return fullPath;
1095
1147
  }
1096
- return existsSync3(resolve5(cwd, "src")) ? resolve5(cwd, "src/index.css") : resolve5(cwd, "index.css");
1148
+ return existsSync3(resolve6(cwd, "src")) ? resolve6(cwd, "src/index.css") : resolve6(cwd, "index.css");
1097
1149
  }
1098
1150
  async function ensureStylesheet(cssPath) {
1099
1151
  if (!existsSync3(cssPath)) {
@@ -1101,7 +1153,7 @@ async function ensureStylesheet(cssPath) {
1101
1153
  await writeFile3(cssPath, '@import "rainbowindex";\n', "utf-8");
1102
1154
  return { changed: true, created: true };
1103
1155
  }
1104
- const original = await readFile3(cssPath, "utf-8");
1156
+ const original = await readFile4(cssPath, "utf-8");
1105
1157
  if (/@import\s+["']rainbowindex["']/.test(original)) {
1106
1158
  return { changed: false, created: false };
1107
1159
  }
@@ -1128,7 +1180,7 @@ ${content}`;
1128
1180
  }
1129
1181
  async function ensureStylesheetImport(entryFile, cssPath) {
1130
1182
  if (!entryFile || !existsSync3(entryFile)) return false;
1131
- const original = await readFile3(entryFile, "utf-8");
1183
+ const original = await readFile4(entryFile, "utf-8");
1132
1184
  const relativePath = toImportPath(dirname3(entryFile), cssPath);
1133
1185
  if (hasStylesheetImport(original, relativePath)) {
1134
1186
  return false;
@@ -1139,16 +1191,16 @@ async function ensureStylesheetImport(entryFile, cssPath) {
1139
1191
  }
1140
1192
  async function findImportedStylesheet(cwd) {
1141
1193
  for (const entryFile of ENTRY_FILES) {
1142
- const fullPath = resolve5(cwd, entryFile);
1194
+ const fullPath = resolve6(cwd, entryFile);
1143
1195
  if (!existsSync3(fullPath)) continue;
1144
- const content = await readFile3(fullPath, "utf-8");
1196
+ const content = await readFile4(fullPath, "utf-8");
1145
1197
  const matches = content.matchAll(
1146
1198
  /import\s+(?:.+?\s+from\s+)?["']([^"']+\.css(?:\?[^"']*)?)["']/g
1147
1199
  );
1148
1200
  for (const match of matches) {
1149
1201
  const importPath = match[1].split("?")[0];
1150
1202
  if (!importPath.startsWith(".")) continue;
1151
- const stylesheet = resolve5(dirname3(fullPath), importPath);
1203
+ const stylesheet = resolve6(dirname3(fullPath), importPath);
1152
1204
  if (existsSync3(stylesheet)) return stylesheet;
1153
1205
  }
1154
1206
  }
@@ -1156,7 +1208,7 @@ async function findImportedStylesheet(cwd) {
1156
1208
  }
1157
1209
  async function findEntryFile(cwd) {
1158
1210
  for (const entryFile of ENTRY_FILES) {
1159
- const fullPath = resolve5(cwd, entryFile);
1211
+ const fullPath = resolve6(cwd, entryFile);
1160
1212
  if (existsSync3(fullPath)) return fullPath;
1161
1213
  }
1162
1214
  return null;
@@ -1166,18 +1218,18 @@ function hasStylesheetImport(source, importPath) {
1166
1218
  return new RegExp(`import\\s+(?:.+?\\s+from\\s+)?["']${escapedPath}["']`).test(source);
1167
1219
  }
1168
1220
  function toImportPath(fromDir, targetFile) {
1169
- const rel = relative2(fromDir, targetFile).replaceAll("\\", "/");
1221
+ const rel = relative3(fromDir, targetFile).replaceAll("\\", "/");
1170
1222
  return rel.startsWith(".") ? rel : `./${rel}`;
1171
1223
  }
1172
1224
  function findExistingViteConfig(cwd) {
1173
1225
  for (const name of VITE_CONFIG_FILES) {
1174
- const fullPath = resolve5(cwd, name);
1226
+ const fullPath = resolve6(cwd, name);
1175
1227
  if (existsSync3(fullPath)) return fullPath;
1176
1228
  }
1177
1229
  return null;
1178
1230
  }
1179
1231
  function chooseViteConfigName(cwd, packageJSON) {
1180
- if (existsSync3(resolve5(cwd, "tsconfig.json")) || existsSync3(resolve5(cwd, "tsconfig.app.json")) || existsSync3(resolve5(cwd, "src/main.ts")) || existsSync3(resolve5(cwd, "src/main.tsx")) || packageJSON?.dependencies?.typescript || packageJSON?.devDependencies?.typescript) {
1232
+ if (existsSync3(resolve6(cwd, "tsconfig.json")) || existsSync3(resolve6(cwd, "tsconfig.app.json")) || existsSync3(resolve6(cwd, "src/main.ts")) || existsSync3(resolve6(cwd, "src/main.tsx")) || packageJSON?.dependencies?.typescript || packageJSON?.devDependencies?.typescript) {
1181
1233
  return "vite.config.ts";
1182
1234
  }
1183
1235
  return packageJSON?.type === "module" ? "vite.config.js" : "vite.config.mjs";
@@ -1225,7 +1277,7 @@ function getInstallCommand(packageManager) {
1225
1277
  }
1226
1278
  }
1227
1279
  function readPackageJSONSync(cwd) {
1228
- const packagePath = resolve5(cwd, "package.json");
1280
+ const packagePath = resolve6(cwd, "package.json");
1229
1281
  if (!existsSync3(packagePath)) return null;
1230
1282
  try {
1231
1283
  return JSON.parse(readFileSync(packagePath, "utf-8"));
@@ -1236,7 +1288,7 @@ function readPackageJSONSync(cwd) {
1236
1288
 
1237
1289
  // src/entries/cli.ts
1238
1290
  function getVersion() {
1239
- return true ? "0.4.1" : "unknown";
1291
+ return true ? "0.5.0" : "unknown";
1240
1292
  }
1241
1293
  async function main() {
1242
1294
  const args = process.argv.slice(2);
@@ -1260,6 +1312,9 @@ async function main() {
1260
1312
  case "preload-fonts":
1261
1313
  await preloadFonts(opts, cwd);
1262
1314
  break;
1315
+ case "scan":
1316
+ await scanFiles(opts, cwd);
1317
+ break;
1263
1318
  case "build": {
1264
1319
  if (opts.watch) {
1265
1320
  await watchMode(opts, cwd);
@@ -1280,7 +1335,7 @@ var isDirectExecution = (() => {
1280
1335
  const entry = process.argv[1];
1281
1336
  if (entry === void 0) return false;
1282
1337
  try {
1283
- return import.meta.url === pathToFileURL(realpathSync(resolve6(entry))).href;
1338
+ return import.meta.url === pathToFileURL(realpathSync(resolve7(entry))).href;
1284
1339
  } catch {
1285
1340
  return false;
1286
1341
  }
package/dist/editor.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  CSS_ENTRY_CANDIDATES,
3
3
  UTILITY_VALUE_SPACES,
4
4
  enumerateClassNames
5
- } from "./chunk-WYMCN5OC.mjs";
5
+ } from "./chunk-NXJZX6KI.mjs";
6
6
  import {
7
7
  isSourceFile
8
8
  } from "./chunk-3HRMFZGE.mjs";
@@ -27,7 +27,7 @@ import {
27
27
  parseUtility,
28
28
  severityForCode,
29
29
  warningCode
30
- } from "./chunk-6GTG5SZJ.mjs";
30
+ } from "./chunk-ZI5ZYNSU.mjs";
31
31
  import {
32
32
  computeDarkStop,
33
33
  defaultTheme,
@@ -383,7 +383,7 @@ function createEditorSession(options = {}) {
383
383
  }
384
384
 
385
385
  // src/entries/editor.ts
386
- var version = true ? "0.4.1" : "0.0.0-dev";
386
+ var version = true ? "0.5.0" : "0.0.0-dev";
387
387
  var EDITOR_API_VERSION = 1;
388
388
  var editorCapabilities = Object.freeze([
389
389
  "class-candidates",
package/dist/index.mjs CHANGED
@@ -3,17 +3,17 @@ import {
3
3
  } from "./chunk-PD4ZXGJ6.mjs";
4
4
  import {
5
5
  postcss_default
6
- } from "./chunk-IYIUS7AE.mjs";
6
+ } from "./chunk-CMB6BHVE.mjs";
7
7
  import {
8
8
  finalizeProjectCompilation,
9
9
  resolveGoogleFonts
10
- } from "./chunk-53WD6U2X.mjs";
10
+ } from "./chunk-DT5HYIM3.mjs";
11
11
  import {
12
12
  analyzeProjectCSS,
13
13
  createCompiler,
14
14
  extractClassesFromSource,
15
15
  pushWarningsDeduped
16
- } from "./chunk-6GTG5SZJ.mjs";
16
+ } from "./chunk-ZI5ZYNSU.mjs";
17
17
  import {
18
18
  DEFAULT_TEXT_SIZES,
19
19
  createCompilationContext,
package/dist/vite.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  postcss_default
3
- } from "./chunk-IYIUS7AE.mjs";
4
- import "./chunk-53WD6U2X.mjs";
3
+ } from "./chunk-CMB6BHVE.mjs";
4
+ import "./chunk-DT5HYIM3.mjs";
5
5
  import {
6
6
  isSourceFile
7
7
  } from "./chunk-3HRMFZGE.mjs";
@@ -12,7 +12,7 @@ import {
12
12
  hasRIActivation,
13
13
  isAtRuleBoundary,
14
14
  isAtRuleNameChar
15
- } from "./chunk-6GTG5SZJ.mjs";
15
+ } from "./chunk-ZI5ZYNSU.mjs";
16
16
  import {
17
17
  devWarn
18
18
  } from "./chunk-KRZL4IDK.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rainbowindex",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "CSS-first system for building and maintaining consistent user interfaces",
5
5
  "keywords": [
6
6
  "css",