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 +71 -0
- package/dist/{chunk-IYIUS7AE.mjs → chunk-CMB6BHVE.mjs} +2 -2
- package/dist/{chunk-53WD6U2X.mjs → chunk-DT5HYIM3.mjs} +15 -22
- package/dist/{chunk-WYMCN5OC.mjs → chunk-NXJZX6KI.mjs} +1 -1
- package/dist/{chunk-6GTG5SZJ.mjs → chunk-ZI5ZYNSU.mjs} +44 -26
- package/dist/cli.mjs +92 -37
- package/dist/editor.mjs +3 -3
- package/dist/index.mjs +3 -3
- package/dist/vite.mjs +3 -3
- package/package.json +1 -1
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
"
|
|
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:
|
|
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: "
|
|
1353
|
+
name: "fieldset-reset",
|
|
1356
1354
|
category: "forms",
|
|
1357
|
-
css: `
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
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:
|
|
1368
|
+
outline-width: 2px;
|
|
1373
1369
|
outline-style: solid;
|
|
1374
|
-
outline-offset:
|
|
1370
|
+
outline-offset: 2px;
|
|
1375
1371
|
outline-color: currentColor;
|
|
1376
|
-
}
|
|
1377
|
-
:focus:not(:focus-visible) {
|
|
1378
|
-
outline: none;
|
|
1379
1372
|
}`
|
|
1380
1373
|
},
|
|
1381
1374
|
{
|
|
@@ -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))
|
|
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 =
|
|
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
|
-
|
|
9632
|
-
|
|
9633
|
-
value,
|
|
9634
|
-
|
|
9635
|
-
|
|
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
|
-
|
|
9811
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
779
|
-
import { dirname as dirname3, relative as
|
|
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 =
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
910
|
+
`[rainbowindex] Created stylesheet: ${relative3(cwd, cssPath).replaceAll("\\", "/")}`
|
|
859
911
|
);
|
|
860
912
|
} else if (cssChanged) {
|
|
861
913
|
console.log(
|
|
862
|
-
`[rainbowindex] Updated stylesheet: ${
|
|
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: ${
|
|
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 =
|
|
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 =
|
|
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(
|
|
908
|
-
if (existsSync3(
|
|
909
|
-
if (existsSync3(
|
|
910
|
-
if (existsSync3(
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
1145
|
+
const fullPath = resolve6(cwd, candidate);
|
|
1094
1146
|
if (existsSync3(fullPath)) return fullPath;
|
|
1095
1147
|
}
|
|
1096
|
-
return existsSync3(
|
|
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
|
|
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
|
|
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 =
|
|
1194
|
+
const fullPath = resolve6(cwd, entryFile);
|
|
1143
1195
|
if (!existsSync3(fullPath)) continue;
|
|
1144
|
-
const content = await
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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.
|
|
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(
|
|
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-
|
|
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-
|
|
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.
|
|
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-
|
|
6
|
+
} from "./chunk-CMB6BHVE.mjs";
|
|
7
7
|
import {
|
|
8
8
|
finalizeProjectCompilation,
|
|
9
9
|
resolveGoogleFonts
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DT5HYIM3.mjs";
|
|
11
11
|
import {
|
|
12
12
|
analyzeProjectCSS,
|
|
13
13
|
createCompiler,
|
|
14
14
|
extractClassesFromSource,
|
|
15
15
|
pushWarningsDeduped
|
|
16
|
-
} from "./chunk-
|
|
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-
|
|
4
|
-
import "./chunk-
|
|
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-
|
|
15
|
+
} from "./chunk-ZI5ZYNSU.mjs";
|
|
16
16
|
import {
|
|
17
17
|
devWarn
|
|
18
18
|
} from "./chunk-KRZL4IDK.mjs";
|