rainbowindex 0.2.2 → 0.4.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.
@@ -5,6 +5,7 @@ import {
5
5
  RI_IMPORT_SPECIFIER_ALTERNATION,
6
6
  SAFE_FONT_FAMILY_RE,
7
7
  SHADOW_VAR_REF_RE,
8
+ analyzeProjectCSS,
8
9
  codepointCompare,
9
10
  compileCSSFunctions,
10
11
  createCompiler,
@@ -18,341 +19,12 @@ import {
18
19
  renderCSS,
19
20
  scanCSSForTokenUsage,
20
21
  withTimeout
21
- } from "./chunk-W6XIBM4M.mjs";
22
+ } from "./chunk-6DAHUFNU.mjs";
22
23
  import {
23
24
  checkPaletteContrast,
24
25
  generateAllColorVariables,
25
26
  generateThemeOverrides
26
- } from "./chunk-SOMDX7V6.mjs";
27
-
28
- // src/scanner/glob-utils.ts
29
- import { isAbsolute, win32 } from "path";
30
- function validateGlobPattern(pattern) {
31
- if (!pattern?.trim()) {
32
- return "Glob pattern is empty.";
33
- }
34
- if (pattern.includes("\0")) {
35
- return "Glob pattern contains a null byte, which is invalid in file paths.";
36
- }
37
- if (isAbsolute(pattern) || win32.isAbsolute(pattern)) {
38
- return `Glob pattern "${pattern}" must be relative, not absolute.`;
39
- }
40
- const segments = pattern.split(/[\\/]+/);
41
- for (const seg of segments) {
42
- if (seg === "..") {
43
- return `Glob pattern "${pattern}" must not traverse parent directories (".."). Restructure your project layout so source files are within the project root, or use @source with a pattern rooted at the project directory.`;
44
- }
45
- }
46
- return null;
47
- }
48
-
49
- // src/scanner/sources.ts
50
- import { readFile, stat } from "fs/promises";
51
- import { resolve as resolve2 } from "path";
52
- import { glob } from "tinyglobby";
53
-
54
- // src/scanner/package-discovery.ts
55
- import { existsSync, readFileSync, realpathSync, statSync } from "fs";
56
- import { dirname, join, posix, resolve } from "path";
57
- var EMPTY = Object.freeze({ sources: [], warnings: [] });
58
- var discoveryCache = /* @__PURE__ */ new Map();
59
- function discoverPackageSafelistSources(cwd) {
60
- const cwdAbs = resolve(cwd);
61
- let mtimeMs;
62
- try {
63
- mtimeMs = statSync(join(cwdAbs, "package.json")).mtimeMs;
64
- } catch {
65
- return EMPTY;
66
- }
67
- const cached = discoveryCache.get(cwdAbs);
68
- if (cached && cached.mtimeMs === mtimeMs) return cached.result;
69
- const result = runDiscovery(cwdAbs);
70
- discoveryCache.set(cwdAbs, { mtimeMs, result });
71
- return result;
72
- }
73
- function runDiscovery(cwdAbs) {
74
- let consumer;
75
- try {
76
- consumer = readPackageJson(join(cwdAbs, "package.json"));
77
- } catch {
78
- return EMPTY;
79
- }
80
- const deps = [
81
- ...Object.keys(consumer.dependencies ?? {}),
82
- ...Object.keys(consumer.peerDependencies ?? {})
83
- ];
84
- if (deps.length === 0) return EMPTY;
85
- const sources = [];
86
- const warnings = [];
87
- for (const depName of deps) {
88
- const depPkgPath = findDepPackageJson(cwdAbs, depName);
89
- if (!depPkgPath) {
90
- continue;
91
- }
92
- let depPkg;
93
- try {
94
- depPkg = readPackageJson(depPkgPath);
95
- } catch (err) {
96
- warnings.push(
97
- `[RI-1410] Could not read ${depName}/package.json during safelist discovery: ${errMessage(err)}`
98
- );
99
- continue;
100
- }
101
- const patterns = depPkg.rainbowindex?.safelistSources;
102
- if (patterns == null) continue;
103
- if (!Array.isArray(patterns)) {
104
- warnings.push(
105
- `[RI-1410] Invalid rainbowindex.safelistSources in ${depName} \u2014 expected an array of glob strings.`
106
- );
107
- continue;
108
- }
109
- if (patterns.length === 0) continue;
110
- const depRoot = realpathOrFallback(dirname(depPkgPath)).replace(/\\/g, "/");
111
- for (const pattern of patterns) {
112
- if (typeof pattern !== "string" || !pattern) {
113
- warnings.push(
114
- `[RI-1410] Invalid rainbowindex.safelistSources entry in ${depName} \u2014 expected a non-empty glob string.`
115
- );
116
- continue;
117
- }
118
- if (validateGlobPattern(pattern) !== null) {
119
- warnings.push(
120
- `[RI-1410] Invalid rainbowindex.safelistSources entry "${pattern}" in ${depName} \u2014 patterns must be relative to the package root and must not traverse parent directories.`
121
- );
122
- continue;
123
- }
124
- const absolute = posix.normalize(`${depRoot}/${pattern.replace(/^\.\//, "")}`);
125
- if (absolute !== depRoot && !absolute.startsWith(`${depRoot}/`)) {
126
- warnings.push(
127
- `[RI-1410] Invalid rainbowindex.safelistSources entry "${pattern}" in ${depName} \u2014 resolved outside the package root.`
128
- );
129
- continue;
130
- }
131
- sources.push({ pattern: absolute, negated: false, inline: false, absolute: true });
132
- }
133
- }
134
- return { sources, warnings };
135
- }
136
- function readPackageJson(path) {
137
- const raw = readFileSync(path, "utf8");
138
- return JSON.parse(raw);
139
- }
140
- function findDepPackageJson(cwd, depName) {
141
- let dir = cwd;
142
- while (true) {
143
- const candidate = join(dir, "node_modules", depName, "package.json");
144
- if (existsSync(candidate)) return candidate;
145
- const parent = dirname(dir);
146
- if (parent === dir) return null;
147
- dir = parent;
148
- }
149
- }
150
- function realpathOrFallback(path) {
151
- try {
152
- return realpathSync(path);
153
- } catch {
154
- return path;
155
- }
156
- }
157
- function errMessage(err) {
158
- return err instanceof Error ? err.message : String(err);
159
- }
160
-
161
- // src/scanner/sources.ts
162
- var DEFAULT_PATTERNS = Object.freeze([
163
- "index.html",
164
- "src/**/*.{html,js,jsx,ts,tsx,mdx,vue,svelte}"
165
- ]);
166
- var DEFAULT_EXCLUDES = Object.freeze([
167
- "node_modules/**",
168
- "dist/**",
169
- "build/**",
170
- "coverage/**",
171
- "public/**",
172
- "**/*.config.*",
173
- "**/*.d.ts"
174
- ]);
175
- var MAX_FILE_SIZE = 1048576;
176
- var MAX_INLINE_SOURCE_SIZE = 102400;
177
- var GLOB_TIMEOUT_MS = 3e4;
178
- var GLOB_TIMEOUT_MESSAGE = `glob() timed out after ${GLOB_TIMEOUT_MS / 1e3}s \u2014 check for network filesystem issues or overly broad @source patterns`;
179
- function collectPatterns(sources) {
180
- const includePatterns = [];
181
- const nodeModulesIncludePatterns = [];
182
- const excludePatterns = [...DEFAULT_EXCLUDES];
183
- const hasUserPositiveGlobs = sources.some((s) => !s.inline && !s.negated && !s.absolute);
184
- if (!hasUserPositiveGlobs) {
185
- includePatterns.push(...DEFAULT_PATTERNS);
186
- }
187
- const warnings = [];
188
- for (const src of sources) {
189
- if (src.inline) continue;
190
- if (!src.absolute) {
191
- const err = validateGlobPattern(src.pattern);
192
- if (err) {
193
- warnings.push(`[RI-1404] @source pattern rejected: ${err}`);
194
- continue;
195
- }
196
- }
197
- if (src.negated) {
198
- excludePatterns.push(src.pattern);
199
- } else if (src.absolute || src.pattern.replace(/^\.\//, "").startsWith("node_modules/")) {
200
- nodeModulesIncludePatterns.push(src.pattern);
201
- } else {
202
- includePatterns.push(src.pattern);
203
- }
204
- }
205
- return {
206
- includePatterns,
207
- nodeModulesIncludePatterns,
208
- excludePatterns,
209
- warnings,
210
- hasUserPositiveGlobs
211
- };
212
- }
213
- async function resolveSourceFilesAsync(sources, cwd, hasInlineClasses = false) {
214
- const {
215
- includePatterns,
216
- nodeModulesIncludePatterns,
217
- excludePatterns,
218
- warnings,
219
- hasUserPositiveGlobs
220
- } = collectPatterns(sources);
221
- const allIncludes = [...includePatterns, ...nodeModulesIncludePatterns];
222
- if (allIncludes.length === 0) return { files: [], warnings };
223
- try {
224
- const globPasses = [];
225
- if (includePatterns.length > 0) {
226
- globPasses.push(
227
- withTimeout(
228
- glob(includePatterns, { cwd, ignore: excludePatterns }),
229
- GLOB_TIMEOUT_MS,
230
- GLOB_TIMEOUT_MESSAGE
231
- )
232
- );
233
- }
234
- if (nodeModulesIncludePatterns.length > 0) {
235
- const relaxedExcludes = excludePatterns.filter((p) => p !== "node_modules/**");
236
- globPasses.push(
237
- withTimeout(
238
- glob(nodeModulesIncludePatterns, { cwd, ignore: relaxedExcludes }),
239
- GLOB_TIMEOUT_MS,
240
- GLOB_TIMEOUT_MESSAGE
241
- )
242
- );
243
- }
244
- const matched = (await Promise.all(globPasses)).flat();
245
- const files = [...new Set(matched.map((f) => resolve2(cwd, f)))].sort(codepointCompare);
246
- if (files.length === 0 && !(hasInlineClasses && !hasUserPositiveGlobs)) {
247
- warnings.push(
248
- `[RI-1401] No source files found matching ${allIncludes.map((p) => `"${p}"`).join(", ")} \u2014 check @source paths or project structure.`
249
- );
250
- }
251
- return { files, warnings };
252
- } catch (err) {
253
- warnings.push(
254
- `[RI-1402] Invalid glob pattern \u2014 skipping. Check @source syntax. (${err instanceof Error ? err.message : String(err)})`
255
- );
256
- return { files: [], warnings };
257
- }
258
- }
259
- function collectInlineClasses(sources) {
260
- const classes = /* @__PURE__ */ new Set();
261
- const warnings = [];
262
- for (const src of sources) {
263
- if (!src.inline) continue;
264
- const items = src.classes ?? [];
265
- let contentLength = 0;
266
- for (const cls of items) contentLength += cls.length;
267
- if (contentLength > MAX_INLINE_SOURCE_SIZE) {
268
- warnings.push(
269
- `[RI-1406] Inline @source content exceeds ${MAX_INLINE_SOURCE_SIZE} byte limit (${contentLength} bytes) \u2014 skipping.`
270
- );
271
- continue;
272
- }
273
- for (const cls of items) {
274
- if (cls) classes.add(cls);
275
- }
276
- }
277
- return { classes, warnings };
278
- }
279
- var FILE_IO_TIMEOUT_MS = 1e4;
280
- async function scanOneFile(file) {
281
- const warnings = [];
282
- try {
283
- const fileSize = await withTimeout(
284
- stat(file).then((s) => s.size),
285
- FILE_IO_TIMEOUT_MS,
286
- "stat() timed out"
287
- );
288
- if (fileSize > MAX_FILE_SIZE) {
289
- return {
290
- classes: null,
291
- warnings,
292
- failure: `[RI-1405] Skipping source file "${file}" (${fileSize} bytes) \u2014 exceeds ${MAX_FILE_SIZE} byte limit.`
293
- };
294
- }
295
- const content = await withTimeout(
296
- readFile(file, "utf-8"),
297
- FILE_IO_TIMEOUT_MS,
298
- "readFile() timed out"
299
- );
300
- return {
301
- classes: extractClassesFromSource({ path: file, content }, warnings),
302
- warnings,
303
- failure: null
304
- };
305
- } catch (err) {
306
- return {
307
- classes: null,
308
- warnings,
309
- failure: `[RI-1403] Could not read source file "${file}" \u2014 skipping. (${err instanceof Error ? err.message : String(err)})`
310
- };
311
- }
312
- }
313
- async function scanSourceFilesAsync(sources, cwd) {
314
- const { classes: allClasses, warnings: inlineWarnings } = collectInlineClasses(sources);
315
- const allWarnings = [...inlineWarnings];
316
- const { files, warnings: resolveWarnings } = await resolveSourceFilesAsync(
317
- sources,
318
- cwd,
319
- allClasses.size > 0
320
- );
321
- allWarnings.push(...resolveWarnings);
322
- const CONCURRENCY_LIMIT = 32;
323
- const results = new Array(files.length);
324
- let nextIndex = 0;
325
- const worker = async () => {
326
- while (nextIndex < files.length) {
327
- const index = nextIndex++;
328
- results[index] = await scanOneFile(files[index]);
329
- }
330
- };
331
- await Promise.all(Array.from({ length: Math.min(CONCURRENCY_LIMIT, files.length) }, worker));
332
- const seen = new Set(allWarnings);
333
- for (const result of results) {
334
- if (result.failure) {
335
- allWarnings.push(result.failure);
336
- seen.add(result.failure);
337
- continue;
338
- }
339
- if (result.classes) {
340
- for (const cls of result.classes) {
341
- allClasses.add(cls);
342
- }
343
- }
344
- pushWarningsDeduped(allWarnings, result.warnings, seen);
345
- }
346
- return { classes: allClasses, warnings: allWarnings };
347
- }
348
- async function collectProjectClasses(themeSources, surfaceSources, cwd, warnings, warningSeen) {
349
- const discovered = discoverPackageSafelistSources(cwd);
350
- pushWarningsDeduped(warnings, discovered.warnings, warningSeen);
351
- const allSources = [...themeSources, ...surfaceSources, ...discovered.sources];
352
- const scanResult = await scanSourceFilesAsync(allSources, cwd);
353
- pushWarningsDeduped(warnings, scanResult.warnings, warningSeen);
354
- return scanResult.classes;
355
- }
27
+ } from "./chunk-5Y7EXXLS.mjs";
356
28
 
357
29
  // src/integrations/font-providers/google/state.ts
358
30
  var googleFontInternals = {
@@ -366,13 +38,13 @@ var googleFontInternals = {
366
38
  // src/integrations/font-providers/google/cache.ts
367
39
  import { createHash, randomUUID } from "crypto";
368
40
  import { open as fsOpen, writeFile, rename, unlink, mkdir } from "fs/promises";
369
- import { resolve as resolve3, dirname as dirname2, join as join2, isAbsolute as isAbsolute2 } from "path";
41
+ import { resolve, dirname, join, isAbsolute } from "path";
370
42
  import { isAbsolute as win32IsAbsolute } from "path/win32";
371
43
  var MAX_FONT_CACHE_TTL_SECONDS = 30 * 24 * 60 * 60;
372
44
  function getFontCacheDir() {
373
45
  if (googleFontInternals.validatedCacheDir !== null) return googleFontInternals.validatedCacheDir;
374
46
  const raw = process.env.RI_CACHE_DIR || "node_modules/.cache/rainbowindex";
375
- if (isAbsolute2(raw) || win32IsAbsolute(raw)) {
47
+ if (isAbsolute(raw) || win32IsAbsolute(raw)) {
376
48
  throw new Error(
377
49
  `[RI-1208] RI_CACHE_DIR must be a relative path, got absolute path: "${raw}". Use a relative path like "node_modules/.cache/rainbowindex".`
378
50
  );
@@ -392,7 +64,7 @@ function getResolvedCachePath() {
392
64
  if (googleFontInternals.resolvedCachePath !== null && googleFontInternals.validatedCacheDir !== null) {
393
65
  return googleFontInternals.resolvedCachePath;
394
66
  }
395
- googleFontInternals.resolvedCachePath = resolve3(process.cwd(), getFontCacheFile());
67
+ googleFontInternals.resolvedCachePath = resolve(process.cwd(), getFontCacheFile());
396
68
  return googleFontInternals.resolvedCachePath;
397
69
  }
398
70
  function getFontCacheTTL() {
@@ -475,12 +147,12 @@ async function loadFontCache(ignoreExpiry = false) {
475
147
  async function saveFontCache() {
476
148
  try {
477
149
  const cachePath = getResolvedCachePath();
478
- await mkdir(dirname2(cachePath), { recursive: true });
150
+ await mkdir(dirname(cachePath), { recursive: true });
479
151
  const entries = Array.from(googleFontInternals.googleFontState.cache.values());
480
152
  const entriesJson = JSON.stringify(entries);
481
153
  const checksum = createHash("sha256").update(entriesJson).digest("hex");
482
154
  const payload = `{"checksum":${JSON.stringify(checksum)},"entries":${entriesJson}}`;
483
- const tmpPath = join2(dirname2(cachePath), `.google-${process.pid}-${randomUUID()}.tmp`);
155
+ const tmpPath = join(dirname(cachePath), `.google-${process.pid}-${randomUUID()}.tmp`);
484
156
  try {
485
157
  await writeFile(tmpPath, payload);
486
158
  await rename(tmpPath, cachePath);
@@ -753,102 +425,431 @@ function generateWebFontFace(family, face) {
753
425
  if (face.provider === "google") {
754
426
  return { type: "import", css: `@import url("${googleFontsUrl(family, face)}");` };
755
427
  }
756
- const safeProvider = face.provider.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
757
- const format = inferFontFormat(face.provider);
758
- const src = `url("${safeProvider}") format("${format}")`;
759
- const declarations = [` font-family: "${escapeFontFamily(family)}";`, ` src: ${src};`];
760
- if (face.weight) declarations.push(` font-weight: ${face.weight};`);
761
- if (face.style && face.style !== "normal") declarations.push(` font-style: ${face.style};`);
762
- if (face.display) declarations.push(` font-display: ${face.display};`);
763
- if (face.unicodeRange) declarations.push(` unicode-range: ${face.unicodeRange};`);
764
- return { type: "font-face", css: `@font-face {
765
- ${declarations.join("\n")}
766
- }` };
428
+ const safeProvider = face.provider.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
429
+ const format = inferFontFormat(face.provider);
430
+ const src = `url("${safeProvider}") format("${format}")`;
431
+ const declarations = [` font-family: "${escapeFontFamily(family)}";`, ` src: ${src};`];
432
+ if (face.weight) declarations.push(` font-weight: ${face.weight};`);
433
+ if (face.style && face.style !== "normal") declarations.push(` font-style: ${face.style};`);
434
+ if (face.display) declarations.push(` font-display: ${face.display};`);
435
+ if (face.unicodeRange) declarations.push(` unicode-range: ${face.unicodeRange};`);
436
+ return { type: "font-face", css: `@font-face {
437
+ ${declarations.join("\n")}
438
+ }` };
439
+ }
440
+ function normalizeLocalStyle(style, family, warnings) {
441
+ if (style.includes(" ") && !style.startsWith("oblique")) {
442
+ const first = style.split(/\s+/)[0];
443
+ warnings.push(
444
+ `[RI-1203] Local font "${family}" has a compound font-style "${style}" \u2014 a single @font-face takes one style. Split upright and italic into separate @face blocks (or use the italic: shorthand). Using "${first}".`
445
+ );
446
+ return first;
447
+ }
448
+ return style;
449
+ }
450
+ function generateFontCSS(slot) {
451
+ const imports = [];
452
+ const fontFaces = [];
453
+ const variables = [];
454
+ const warnings = [];
455
+ const pushFeatureVars = () => {
456
+ if (slot.features) variables.push(`--font-${slot.slot}--features: ${slot.features};`);
457
+ if (slot.variation) variables.push(`--font-${slot.slot}--variations: ${slot.variation};`);
458
+ };
459
+ if (slot.kind === "system") {
460
+ variables.push(`--font-${slot.slot}: ${getFallbackStack(slot.slot)};`);
461
+ return { imports, fontFaces, variables, warnings };
462
+ }
463
+ if (slot.kind === "manual") {
464
+ const stack = [`"${escapeFontFamily(slot.family)}"`, ...slot.fallback].join(", ");
465
+ variables.push(`--font-${slot.slot}: ${stack};`);
466
+ pushFeatureVars();
467
+ return { imports, fontFaces, variables, warnings };
468
+ }
469
+ const { sizeAdjust, ascent, descent, lineGap } = slot;
470
+ const hasMetrics = sizeAdjust !== void 0 && ascent !== void 0 && descent !== void 0 && lineGap !== void 0;
471
+ if (hasMetrics) {
472
+ const metricsFallbackFont = slot.metricsFallback || slot.fallback[0] || "Arial";
473
+ fontFaces.push(
474
+ generateFallbackFontFace(slot.family, {
475
+ fallback: metricsFallbackFont,
476
+ sizeAdjust,
477
+ ascent,
478
+ descent,
479
+ lineGap
480
+ })
481
+ );
482
+ }
483
+ for (const face of slot.faces) {
484
+ if (face.provider !== "google" && !face.provider.startsWith("/") && !face.provider.startsWith("http") && !face.provider.startsWith(".")) {
485
+ warnings.push(
486
+ `[RI-1201] Unknown font provider "${face.provider}" for "${slot.family}" \u2014 supported: google, or a file path/URL.`
487
+ );
488
+ continue;
489
+ }
490
+ const emitFace = face.provider === "google" ? face : { ...face, style: normalizeLocalStyle(face.style, slot.family, warnings) };
491
+ const webFont = generateWebFontFace(slot.family, emitFace);
492
+ if (!webFont) continue;
493
+ if (webFont.type === "import") imports.push(webFont.css);
494
+ else fontFaces.push(webFont.css);
495
+ }
496
+ const fallbackStack = slot.fallback.length > 0 ? slot.fallback.join(", ") : getFallbackStack(slot.slot);
497
+ const safeFamily = escapeFontFamily(slot.family);
498
+ const stackParts = [`"${safeFamily}"`];
499
+ if (hasMetrics) stackParts.push(`"${safeFamily} Fallback"`);
500
+ stackParts.push(fallbackStack);
501
+ variables.push(`--font-${slot.slot}: ${stackParts.join(", ")};`);
502
+ pushFeatureVars();
503
+ return { imports, fontFaces, variables, warnings };
504
+ }
505
+ function getFontPreloadLinks(slots) {
506
+ const links = [];
507
+ const seen = /* @__PURE__ */ new Set();
508
+ for (const slot of slots) {
509
+ for (const face of slot.faces) {
510
+ const preload = face.preload ?? slot.preload;
511
+ if (!preload) continue;
512
+ if (face.provider === "system" || face.provider === "google" || !face.provider) continue;
513
+ if (seen.has(face.provider)) continue;
514
+ seen.add(face.provider);
515
+ links.push({
516
+ href: face.provider,
517
+ as: "font",
518
+ type: FONT_FORMAT_MIME[inferFontFormat(face.provider)],
519
+ crossorigin: true
520
+ });
521
+ }
522
+ }
523
+ return links;
524
+ }
525
+
526
+ // src/scanner/sources.ts
527
+ import { readFile, stat } from "fs/promises";
528
+ import { resolve as resolve3 } from "path";
529
+ import { glob } from "tinyglobby";
530
+
531
+ // src/scanner/glob-utils.ts
532
+ import { isAbsolute as isAbsolute2, win32 } from "path";
533
+ function validateGlobPattern(pattern) {
534
+ if (!pattern?.trim()) {
535
+ return "Glob pattern is empty.";
536
+ }
537
+ if (pattern.includes("\0")) {
538
+ return "Glob pattern contains a null byte, which is invalid in file paths.";
539
+ }
540
+ if (isAbsolute2(pattern) || win32.isAbsolute(pattern)) {
541
+ return `Glob pattern "${pattern}" must be relative, not absolute.`;
542
+ }
543
+ const segments = pattern.split(/[\\/]+/);
544
+ for (const seg of segments) {
545
+ if (seg === "..") {
546
+ return `Glob pattern "${pattern}" must not traverse parent directories (".."). Restructure your project layout so source files are within the project root, or use @source with a pattern rooted at the project directory.`;
547
+ }
548
+ }
549
+ return null;
550
+ }
551
+
552
+ // src/scanner/package-discovery.ts
553
+ import { existsSync, readFileSync, realpathSync, statSync } from "fs";
554
+ import { dirname as dirname2, join as join2, posix, resolve as resolve2 } from "path";
555
+ var EMPTY = Object.freeze({ sources: [], warnings: [] });
556
+ var discoveryCache = /* @__PURE__ */ new Map();
557
+ function discoverPackageSafelistSources(cwd) {
558
+ const cwdAbs = resolve2(cwd);
559
+ let mtimeMs;
560
+ try {
561
+ mtimeMs = statSync(join2(cwdAbs, "package.json")).mtimeMs;
562
+ } catch {
563
+ return EMPTY;
564
+ }
565
+ const cached = discoveryCache.get(cwdAbs);
566
+ if (cached && cached.mtimeMs === mtimeMs) return cached.result;
567
+ const result = runDiscovery(cwdAbs);
568
+ discoveryCache.set(cwdAbs, { mtimeMs, result });
569
+ return result;
570
+ }
571
+ function runDiscovery(cwdAbs) {
572
+ let consumer;
573
+ try {
574
+ consumer = readPackageJson(join2(cwdAbs, "package.json"));
575
+ } catch {
576
+ return EMPTY;
577
+ }
578
+ const deps = [
579
+ ...Object.keys(consumer.dependencies ?? {}),
580
+ ...Object.keys(consumer.peerDependencies ?? {})
581
+ ];
582
+ if (deps.length === 0) return EMPTY;
583
+ const sources = [];
584
+ const warnings = [];
585
+ for (const depName of deps) {
586
+ const depPkgPath = findDepPackageJson(cwdAbs, depName);
587
+ if (!depPkgPath) {
588
+ continue;
589
+ }
590
+ let depPkg;
591
+ try {
592
+ depPkg = readPackageJson(depPkgPath);
593
+ } catch (err) {
594
+ warnings.push(
595
+ `[RI-1410] Could not read ${depName}/package.json during safelist discovery: ${errMessage(err)}`
596
+ );
597
+ continue;
598
+ }
599
+ const patterns = depPkg.rainbowindex?.safelistSources;
600
+ if (patterns == null) continue;
601
+ if (!Array.isArray(patterns)) {
602
+ warnings.push(
603
+ `[RI-1410] Invalid rainbowindex.safelistSources in ${depName} \u2014 expected an array of glob strings.`
604
+ );
605
+ continue;
606
+ }
607
+ if (patterns.length === 0) continue;
608
+ const depRoot = realpathOrFallback(dirname2(depPkgPath)).replace(/\\/g, "/");
609
+ for (const pattern of patterns) {
610
+ if (typeof pattern !== "string" || !pattern) {
611
+ warnings.push(
612
+ `[RI-1410] Invalid rainbowindex.safelistSources entry in ${depName} \u2014 expected a non-empty glob string.`
613
+ );
614
+ continue;
615
+ }
616
+ if (validateGlobPattern(pattern) !== null) {
617
+ warnings.push(
618
+ `[RI-1410] Invalid rainbowindex.safelistSources entry "${pattern}" in ${depName} \u2014 patterns must be relative to the package root and must not traverse parent directories.`
619
+ );
620
+ continue;
621
+ }
622
+ const absolute = posix.normalize(`${depRoot}/${pattern.replace(/^\.\//, "")}`);
623
+ if (absolute !== depRoot && !absolute.startsWith(`${depRoot}/`)) {
624
+ warnings.push(
625
+ `[RI-1410] Invalid rainbowindex.safelistSources entry "${pattern}" in ${depName} \u2014 resolved outside the package root.`
626
+ );
627
+ continue;
628
+ }
629
+ sources.push({ pattern: absolute, negated: false, inline: false, absolute: true });
630
+ }
631
+ }
632
+ return { sources, warnings };
633
+ }
634
+ function readPackageJson(path) {
635
+ const raw = readFileSync(path, "utf8");
636
+ return JSON.parse(raw);
637
+ }
638
+ function findDepPackageJson(cwd, depName) {
639
+ let dir = cwd;
640
+ while (true) {
641
+ const candidate = join2(dir, "node_modules", depName, "package.json");
642
+ if (existsSync(candidate)) return candidate;
643
+ const parent = dirname2(dir);
644
+ if (parent === dir) return null;
645
+ dir = parent;
646
+ }
647
+ }
648
+ function realpathOrFallback(path) {
649
+ try {
650
+ return realpathSync(path);
651
+ } catch {
652
+ return path;
653
+ }
654
+ }
655
+ function errMessage(err) {
656
+ return err instanceof Error ? err.message : String(err);
657
+ }
658
+
659
+ // src/scanner/sources.ts
660
+ var DEFAULT_PATTERNS = Object.freeze([
661
+ "index.html",
662
+ "src/**/*.{html,js,jsx,ts,tsx,mdx,vue,svelte}"
663
+ ]);
664
+ var DEFAULT_EXCLUDES = Object.freeze([
665
+ "node_modules/**",
666
+ "dist/**",
667
+ "build/**",
668
+ "coverage/**",
669
+ "public/**",
670
+ "**/*.config.*",
671
+ "**/*.d.ts"
672
+ ]);
673
+ var MAX_FILE_SIZE = 1048576;
674
+ var MAX_INLINE_SOURCE_SIZE = 102400;
675
+ var GLOB_TIMEOUT_MS = 3e4;
676
+ var GLOB_TIMEOUT_MESSAGE = `glob() timed out after ${GLOB_TIMEOUT_MS / 1e3}s \u2014 check for network filesystem issues or overly broad @source patterns`;
677
+ function collectPatterns(sources) {
678
+ const includePatterns = [];
679
+ const nodeModulesIncludePatterns = [];
680
+ const excludePatterns = [...DEFAULT_EXCLUDES];
681
+ const hasUserPositiveGlobs = sources.some((s) => !s.inline && !s.negated && !s.absolute);
682
+ if (!hasUserPositiveGlobs) {
683
+ includePatterns.push(...DEFAULT_PATTERNS);
684
+ }
685
+ const warnings = [];
686
+ for (const src of sources) {
687
+ if (src.inline) continue;
688
+ if (!src.absolute) {
689
+ const err = validateGlobPattern(src.pattern);
690
+ if (err) {
691
+ warnings.push(`[RI-1404] @source pattern rejected: ${err}`);
692
+ continue;
693
+ }
694
+ }
695
+ if (src.negated) {
696
+ excludePatterns.push(src.pattern);
697
+ } else if (src.absolute || src.pattern.replace(/^\.\//, "").startsWith("node_modules/")) {
698
+ nodeModulesIncludePatterns.push(src.pattern);
699
+ } else {
700
+ includePatterns.push(src.pattern);
701
+ }
702
+ }
703
+ return {
704
+ includePatterns,
705
+ nodeModulesIncludePatterns,
706
+ excludePatterns,
707
+ warnings,
708
+ hasUserPositiveGlobs
709
+ };
767
710
  }
768
- function normalizeLocalStyle(style, family, warnings) {
769
- if (style.includes(" ") && !style.startsWith("oblique")) {
770
- const first = style.split(/\s+/)[0];
711
+ async function resolveSourceFilesAsync(sources, cwd, hasInlineClasses = false) {
712
+ const {
713
+ includePatterns,
714
+ nodeModulesIncludePatterns,
715
+ excludePatterns,
716
+ warnings,
717
+ hasUserPositiveGlobs
718
+ } = collectPatterns(sources);
719
+ const allIncludes = [...includePatterns, ...nodeModulesIncludePatterns];
720
+ if (allIncludes.length === 0) return { files: [], warnings };
721
+ try {
722
+ const globPasses = [];
723
+ if (includePatterns.length > 0) {
724
+ globPasses.push(
725
+ withTimeout(
726
+ glob(includePatterns, { cwd, ignore: excludePatterns }),
727
+ GLOB_TIMEOUT_MS,
728
+ GLOB_TIMEOUT_MESSAGE
729
+ )
730
+ );
731
+ }
732
+ if (nodeModulesIncludePatterns.length > 0) {
733
+ const relaxedExcludes = excludePatterns.filter((p) => p !== "node_modules/**");
734
+ globPasses.push(
735
+ withTimeout(
736
+ glob(nodeModulesIncludePatterns, { cwd, ignore: relaxedExcludes }),
737
+ GLOB_TIMEOUT_MS,
738
+ GLOB_TIMEOUT_MESSAGE
739
+ )
740
+ );
741
+ }
742
+ const matched = (await Promise.all(globPasses)).flat();
743
+ const files = [...new Set(matched.map((f) => resolve3(cwd, f)))].sort(codepointCompare);
744
+ if (files.length === 0 && !(hasInlineClasses && !hasUserPositiveGlobs)) {
745
+ warnings.push(
746
+ `[RI-1401] No source files found matching ${allIncludes.map((p) => `"${p}"`).join(", ")} \u2014 check @source paths or project structure.`
747
+ );
748
+ }
749
+ return { files, warnings };
750
+ } catch (err) {
771
751
  warnings.push(
772
- `[RI-1203] Local font "${family}" has a compound font-style "${style}" \u2014 a single @font-face takes one style. Split upright and italic into separate @face blocks (or use the italic: shorthand). Using "${first}".`
752
+ `[RI-1402] Invalid glob pattern \u2014 skipping. Check @source syntax. (${err instanceof Error ? err.message : String(err)})`
773
753
  );
774
- return first;
754
+ return { files: [], warnings };
775
755
  }
776
- return style;
777
756
  }
778
- function generateFontCSS(slot) {
779
- const imports = [];
780
- const fontFaces = [];
781
- const variables = [];
757
+ function collectInlineClasses(sources) {
758
+ const classes = /* @__PURE__ */ new Set();
782
759
  const warnings = [];
783
- const pushFeatureVars = () => {
784
- if (slot.features) variables.push(`--font-${slot.slot}--features: ${slot.features};`);
785
- if (slot.variation) variables.push(`--font-${slot.slot}--variations: ${slot.variation};`);
786
- };
787
- if (slot.kind === "system") {
788
- variables.push(`--font-${slot.slot}: ${getFallbackStack(slot.slot)};`);
789
- return { imports, fontFaces, variables, warnings };
790
- }
791
- if (slot.kind === "manual") {
792
- const stack = [`"${escapeFontFamily(slot.family)}"`, ...slot.fallback].join(", ");
793
- variables.push(`--font-${slot.slot}: ${stack};`);
794
- pushFeatureVars();
795
- return { imports, fontFaces, variables, warnings };
796
- }
797
- const { sizeAdjust, ascent, descent, lineGap } = slot;
798
- const hasMetrics = sizeAdjust !== void 0 && ascent !== void 0 && descent !== void 0 && lineGap !== void 0;
799
- if (hasMetrics) {
800
- const metricsFallbackFont = slot.metricsFallback || slot.fallback[0] || "Arial";
801
- fontFaces.push(
802
- generateFallbackFontFace(slot.family, {
803
- fallback: metricsFallbackFont,
804
- sizeAdjust,
805
- ascent,
806
- descent,
807
- lineGap
808
- })
809
- );
810
- }
811
- for (const face of slot.faces) {
812
- if (face.provider !== "google" && !face.provider.startsWith("/") && !face.provider.startsWith("http") && !face.provider.startsWith(".")) {
760
+ for (const src of sources) {
761
+ if (!src.inline) continue;
762
+ const items = src.classes ?? [];
763
+ let contentLength = 0;
764
+ for (const cls of items) contentLength += cls.length;
765
+ if (contentLength > MAX_INLINE_SOURCE_SIZE) {
813
766
  warnings.push(
814
- `[RI-1201] Unknown font provider "${face.provider}" for "${slot.family}" \u2014 supported: google, or a file path/URL.`
767
+ `[RI-1406] Inline @source content exceeds ${MAX_INLINE_SOURCE_SIZE} byte limit (${contentLength} bytes) \u2014 skipping.`
815
768
  );
816
769
  continue;
817
770
  }
818
- const emitFace = face.provider === "google" ? face : { ...face, style: normalizeLocalStyle(face.style, slot.family, warnings) };
819
- const webFont = generateWebFontFace(slot.family, emitFace);
820
- if (!webFont) continue;
821
- if (webFont.type === "import") imports.push(webFont.css);
822
- else fontFaces.push(webFont.css);
771
+ for (const cls of items) {
772
+ if (cls) classes.add(cls);
773
+ }
823
774
  }
824
- const fallbackStack = slot.fallback.length > 0 ? slot.fallback.join(", ") : getFallbackStack(slot.slot);
825
- const safeFamily = escapeFontFamily(slot.family);
826
- const stackParts = [`"${safeFamily}"`];
827
- if (hasMetrics) stackParts.push(`"${safeFamily} Fallback"`);
828
- stackParts.push(fallbackStack);
829
- variables.push(`--font-${slot.slot}: ${stackParts.join(", ")};`);
830
- pushFeatureVars();
831
- return { imports, fontFaces, variables, warnings };
775
+ return { classes, warnings };
832
776
  }
833
- function getFontPreloadLinks(slots) {
834
- const links = [];
835
- const seen = /* @__PURE__ */ new Set();
836
- for (const slot of slots) {
837
- for (const face of slot.faces) {
838
- const preload = face.preload ?? slot.preload;
839
- if (!preload) continue;
840
- if (face.provider === "system" || face.provider === "google" || !face.provider) continue;
841
- if (seen.has(face.provider)) continue;
842
- seen.add(face.provider);
843
- links.push({
844
- href: face.provider,
845
- as: "font",
846
- type: FONT_FORMAT_MIME[inferFontFormat(face.provider)],
847
- crossorigin: true
848
- });
777
+ var FILE_IO_TIMEOUT_MS = 1e4;
778
+ async function scanOneFile(file) {
779
+ const warnings = [];
780
+ try {
781
+ const fileSize = await withTimeout(
782
+ stat(file).then((s) => s.size),
783
+ FILE_IO_TIMEOUT_MS,
784
+ "stat() timed out"
785
+ );
786
+ if (fileSize > MAX_FILE_SIZE) {
787
+ return {
788
+ classes: null,
789
+ warnings,
790
+ failure: `[RI-1405] Skipping source file "${file}" (${fileSize} bytes) \u2014 exceeds ${MAX_FILE_SIZE} byte limit.`
791
+ };
849
792
  }
793
+ const content = await withTimeout(
794
+ readFile(file, "utf-8"),
795
+ FILE_IO_TIMEOUT_MS,
796
+ "readFile() timed out"
797
+ );
798
+ return {
799
+ classes: extractClassesFromSource({ path: file, content }, warnings),
800
+ warnings,
801
+ failure: null
802
+ };
803
+ } catch (err) {
804
+ return {
805
+ classes: null,
806
+ warnings,
807
+ failure: `[RI-1403] Could not read source file "${file}" \u2014 skipping. (${err instanceof Error ? err.message : String(err)})`
808
+ };
850
809
  }
851
- return links;
810
+ }
811
+ async function scanSourceFilesAsync(sources, cwd) {
812
+ const { classes: allClasses, warnings: inlineWarnings } = collectInlineClasses(sources);
813
+ const allWarnings = [...inlineWarnings];
814
+ const { files, warnings: resolveWarnings } = await resolveSourceFilesAsync(
815
+ sources,
816
+ cwd,
817
+ allClasses.size > 0
818
+ );
819
+ allWarnings.push(...resolveWarnings);
820
+ const CONCURRENCY_LIMIT = 32;
821
+ const results = new Array(files.length);
822
+ let nextIndex = 0;
823
+ const worker = async () => {
824
+ while (nextIndex < files.length) {
825
+ const index = nextIndex++;
826
+ results[index] = await scanOneFile(files[index]);
827
+ }
828
+ };
829
+ await Promise.all(Array.from({ length: Math.min(CONCURRENCY_LIMIT, files.length) }, worker));
830
+ const seen = new Set(allWarnings);
831
+ for (const result of results) {
832
+ if (result.failure) {
833
+ allWarnings.push(result.failure);
834
+ seen.add(result.failure);
835
+ continue;
836
+ }
837
+ if (result.classes) {
838
+ for (const cls of result.classes) {
839
+ allClasses.add(cls);
840
+ }
841
+ }
842
+ pushWarningsDeduped(allWarnings, result.warnings, seen);
843
+ }
844
+ return { classes: allClasses, warnings: allWarnings };
845
+ }
846
+ async function collectProjectClasses(themeSources, surfaceSources, cwd, warnings, warningSeen) {
847
+ const discovered = discoverPackageSafelistSources(cwd);
848
+ pushWarningsDeduped(warnings, discovered.warnings, warningSeen);
849
+ const allSources = [...themeSources, ...surfaceSources, ...discovered.sources];
850
+ const scanResult = await scanSourceFilesAsync(allSources, cwd);
851
+ pushWarningsDeduped(warnings, scanResult.warnings, warningSeen);
852
+ return scanResult.classes;
852
853
  }
853
854
 
854
855
  // src/css/strip.ts
@@ -1459,7 +1460,7 @@ function collectPropertyBlocks(registered, engineProperties) {
1459
1460
  return blocks;
1460
1461
  }
1461
1462
  function assembleSections(compilation, theme, fontOutputCache) {
1462
- const sections = [];
1463
+ const baseSections = [];
1463
1464
  const fontImports = [];
1464
1465
  const fontFaceBlocks = [];
1465
1466
  const assemblyWarnings = [];
@@ -1480,18 +1481,16 @@ function assembleSections(compilation, theme, fontOutputCache) {
1480
1481
  }
1481
1482
  }
1482
1483
  }
1483
- if (fontImports.length > 0) {
1484
- sections.push(fontImports.join("\n"));
1485
- }
1484
+ const importsSection = fontImports.length > 0 ? fontImports.join("\n") : null;
1486
1485
  const propertyBlocks = collectPropertyBlocks(theme.registeredProperties, compilation.properties);
1487
1486
  if (propertyBlocks.length > 0) {
1488
- sections.push(propertyBlocks.join("\n\n"));
1487
+ baseSections.push(propertyBlocks.join("\n\n"));
1489
1488
  }
1490
1489
  if (fontFaceBlocks.length > 0) {
1491
- sections.push(fontFaceBlocks.join("\n\n"));
1490
+ baseSections.push(fontFaceBlocks.join("\n\n"));
1492
1491
  }
1493
1492
  const tokenLayer = generateTokenLayer(theme, compilation, fontOutputCache);
1494
- if (tokenLayer) sections.push(tokenLayer);
1493
+ if (tokenLayer) baseSections.push(tokenLayer);
1495
1494
  for (const w of checkPaletteContrast(theme.colors, compilation.usedColorStops)) {
1496
1495
  assemblyWarnings.push(w);
1497
1496
  }
@@ -1500,25 +1499,32 @@ function assembleSections(compilation, theme, fontOutputCache) {
1500
1499
  compilation.usedColorStops.get("theme")
1501
1500
  );
1502
1501
  if (themeOverrides.length > 0) {
1503
- sections.push(themeOverrides.join("\n\n"));
1502
+ baseSections.push(themeOverrides.join("\n\n"));
1504
1503
  }
1505
1504
  const cornerShape = generateCornerShapeBlock(theme);
1506
- if (cornerShape) sections.push(cornerShape);
1505
+ if (cornerShape) baseSections.push(cornerShape);
1507
1506
  if (compilation.keyframes.length > 0) {
1508
- sections.push(compilation.keyframes.join("\n\n"));
1507
+ baseSections.push(compilation.keyframes.join("\n\n"));
1509
1508
  }
1510
1509
  const preflight = generatePreflight(theme.preflight);
1511
- if (preflight) sections.push(preflight);
1510
+ if (preflight) baseSections.push(preflight);
1512
1511
  const utilityCSS = renderCSS({
1513
1512
  ...compilation,
1514
1513
  properties: [],
1515
1514
  keyframes: []
1516
1515
  });
1517
- if (utilityCSS) sections.push(utilityCSS);
1518
- if (theme.layer) {
1519
- applyLayerWrapping(sections, fontImports, theme.layer, utilityCSS !== "");
1520
- }
1521
- return { sections, fontImports, warnings: assemblyWarnings };
1516
+ const utilitiesSection = utilityCSS !== "" ? utilityCSS : null;
1517
+ const parts = {
1518
+ imports: importsSection,
1519
+ base: baseSections,
1520
+ utilities: utilitiesSection
1521
+ };
1522
+ const sections = theme.layer ? applyLayerWrapping(parts, theme.layer) : [
1523
+ ...importsSection !== null ? [importsSection] : [],
1524
+ ...baseSections,
1525
+ ...utilitiesSection !== null ? [utilitiesSection] : []
1526
+ ];
1527
+ return { sections, warnings: assemblyWarnings };
1522
1528
  }
1523
1529
  function wrapInLayer(content, layerName) {
1524
1530
  const indented = content.replace(/^(?=.)/gm, " ");
@@ -1526,42 +1532,36 @@ function wrapInLayer(content, layerName) {
1526
1532
  ${indented}
1527
1533
  }`;
1528
1534
  }
1529
- function applyLayerWrapping(sections, fontImports, layer, hasUtilitySection) {
1530
- const hasImportSection = fontImports.length > 0 && sections.length > 0;
1531
- const importSection = hasImportSection ? sections[0] : null;
1532
- const contentSections = hasImportSection ? sections.slice(1) : [...sections];
1535
+ function applyLayerWrapping(parts, layer) {
1536
+ const sections = [];
1537
+ if (parts.imports !== null) sections.push(parts.imports);
1533
1538
  if (layer.wrapAll) {
1534
- const joined = contentSections.join("\n\n");
1535
- sections.length = 0;
1536
- if (importSection) sections.push(importSection);
1537
1539
  sections.push(`@layer ${layer.wrapAll};`);
1540
+ const joined = [...parts.base, ...parts.utilities !== null ? [parts.utilities] : []].join(
1541
+ "\n\n"
1542
+ );
1538
1543
  if (joined) sections.push(wrapInLayer(joined, layer.wrapAll));
1539
- return;
1544
+ return sections;
1540
1545
  }
1541
- const utilitySection = hasUtilitySection ? contentSections.pop() ?? null : null;
1542
- const baseSections = contentSections;
1543
- const finalSections = [];
1544
- if (importSection) finalSections.push(importSection);
1545
1546
  if (layer.order && layer.order.length > 0) {
1546
- finalSections.push(`@layer ${layer.order.join(", ")};`);
1547
+ sections.push(`@layer ${layer.order.join(", ")};`);
1547
1548
  }
1548
- if (baseSections.length > 0) {
1549
- const baseJoined = baseSections.join("\n\n");
1549
+ if (parts.base.length > 0) {
1550
+ const baseJoined = parts.base.join("\n\n");
1550
1551
  if (layer.base) {
1551
- finalSections.push(wrapInLayer(baseJoined, layer.base));
1552
+ sections.push(wrapInLayer(baseJoined, layer.base));
1552
1553
  } else {
1553
- finalSections.push(baseJoined);
1554
+ sections.push(baseJoined);
1554
1555
  }
1555
1556
  }
1556
- if (utilitySection) {
1557
+ if (parts.utilities !== null) {
1557
1558
  if (layer.utilities) {
1558
- finalSections.push(wrapInLayer(utilitySection, layer.utilities));
1559
+ sections.push(wrapInLayer(parts.utilities, layer.utilities));
1559
1560
  } else {
1560
- finalSections.push(utilitySection);
1561
+ sections.push(parts.utilities);
1561
1562
  }
1562
1563
  }
1563
- sections.length = 0;
1564
- sections.push(...finalSections);
1564
+ return sections;
1565
1565
  }
1566
1566
 
1567
1567
  // src/project/pipeline.ts
@@ -1624,12 +1624,46 @@ async function finalizeProjectCompilation(options) {
1624
1624
  };
1625
1625
  }
1626
1626
 
1627
+ // src/project/scan.ts
1628
+ async function compileScannedProject(options) {
1629
+ const analysis = analyzeProjectCSS(options.css);
1630
+ const resolveFonts = options.resolveFonts ?? resolveGoogleFonts;
1631
+ const fontsReady = Promise.resolve(resolveFonts(analysis.theme.fonts));
1632
+ fontsReady.catch(() => {
1633
+ });
1634
+ const surfaceSources = [];
1635
+ for (const pattern of options.surfacePatterns ?? []) {
1636
+ const error = validateGlobPattern(pattern);
1637
+ if (error) {
1638
+ const warning = options.onInvalidPattern(error);
1639
+ if (warning !== void 0) {
1640
+ pushWarningsDeduped(analysis.warnings, [warning], analysis.warningSeen);
1641
+ }
1642
+ continue;
1643
+ }
1644
+ surfaceSources.push({ pattern, negated: false, inline: false });
1645
+ }
1646
+ const classNames = await collectProjectClasses(
1647
+ analysis.theme.sources,
1648
+ surfaceSources,
1649
+ options.cwd,
1650
+ analysis.warnings,
1651
+ analysis.warningSeen
1652
+ );
1653
+ const compiled = await finalizeProjectCompilation({
1654
+ css: options.css,
1655
+ classNames,
1656
+ analysis,
1657
+ resolveFonts: () => fontsReady
1658
+ });
1659
+ return { compiled, warningSeen: analysis.warningSeen };
1660
+ }
1661
+
1627
1662
  export {
1628
- validateGlobPattern,
1629
- DEFAULT_PATTERNS,
1630
- DEFAULT_EXCLUDES,
1631
- collectProjectClasses,
1632
1663
  resolveGoogleFonts,
1633
1664
  getFontPreloadLinks,
1634
- finalizeProjectCompilation
1665
+ DEFAULT_PATTERNS,
1666
+ DEFAULT_EXCLUDES,
1667
+ finalizeProjectCompilation,
1668
+ compileScannedProject
1635
1669
  };