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.
- package/CHANGELOG.md +70 -14
- package/LICENSE +1 -1
- package/README.md +23 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.mjs +1 -1
- package/dist/chunk-3HRMFZGE.mjs +19 -0
- package/dist/{chunk-FHATRQMN.mjs → chunk-4SVFFDS2.mjs} +495 -461
- package/dist/{chunk-SOMDX7V6.mjs → chunk-5Y7EXXLS.mjs} +320 -417
- package/dist/{chunk-W6XIBM4M.mjs → chunk-6DAHUFNU.mjs} +5278 -4928
- package/dist/{chunk-CUUW2K35.mjs → chunk-TLR6RP5L.mjs} +41 -120
- package/dist/chunk-YLB6FGIG.mjs +244 -0
- package/dist/cli.mjs +280 -277
- package/dist/{index-CfDtWufj.d.ts → context-ruu2x_jR.d.ts} +15 -70
- package/dist/editor.d.ts +106 -28
- package/dist/editor.mjs +44 -12
- package/dist/{index-DK6APAGD.d.ts → index-BB2HoeMj.d.ts} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +4 -4
- package/dist/safelist-D9-Plqta.d.ts +109 -0
- package/dist/vite.mjs +48 -27
- package/package.json +1 -1
- package/dist/chunk-KYDEHYIE.mjs +0 -691
- package/dist/safelist-CH3_PywB.d.ts +0 -43
|
@@ -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-
|
|
22
|
+
} from "./chunk-6DAHUFNU.mjs";
|
|
22
23
|
import {
|
|
23
24
|
checkPaletteContrast,
|
|
24
25
|
generateAllColorVariables,
|
|
25
26
|
generateThemeOverrides
|
|
26
|
-
} from "./chunk-
|
|
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
|
|
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 (
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
769
|
-
|
|
770
|
-
|
|
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-
|
|
752
|
+
`[RI-1402] Invalid glob pattern \u2014 skipping. Check @source syntax. (${err instanceof Error ? err.message : String(err)})`
|
|
773
753
|
);
|
|
774
|
-
return
|
|
754
|
+
return { files: [], warnings };
|
|
775
755
|
}
|
|
776
|
-
return style;
|
|
777
756
|
}
|
|
778
|
-
function
|
|
779
|
-
const
|
|
780
|
-
const fontFaces = [];
|
|
781
|
-
const variables = [];
|
|
757
|
+
function collectInlineClasses(sources) {
|
|
758
|
+
const classes = /* @__PURE__ */ new Set();
|
|
782
759
|
const warnings = [];
|
|
783
|
-
const
|
|
784
|
-
if (
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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-
|
|
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
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
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
|
-
|
|
834
|
-
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1487
|
+
baseSections.push(propertyBlocks.join("\n\n"));
|
|
1489
1488
|
}
|
|
1490
1489
|
if (fontFaceBlocks.length > 0) {
|
|
1491
|
-
|
|
1490
|
+
baseSections.push(fontFaceBlocks.join("\n\n"));
|
|
1492
1491
|
}
|
|
1493
1492
|
const tokenLayer = generateTokenLayer(theme, compilation, fontOutputCache);
|
|
1494
|
-
if (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
|
-
|
|
1502
|
+
baseSections.push(themeOverrides.join("\n\n"));
|
|
1504
1503
|
}
|
|
1505
1504
|
const cornerShape = generateCornerShapeBlock(theme);
|
|
1506
|
-
if (cornerShape)
|
|
1505
|
+
if (cornerShape) baseSections.push(cornerShape);
|
|
1507
1506
|
if (compilation.keyframes.length > 0) {
|
|
1508
|
-
|
|
1507
|
+
baseSections.push(compilation.keyframes.join("\n\n"));
|
|
1509
1508
|
}
|
|
1510
1509
|
const preflight = generatePreflight(theme.preflight);
|
|
1511
|
-
if (preflight)
|
|
1510
|
+
if (preflight) baseSections.push(preflight);
|
|
1512
1511
|
const utilityCSS = renderCSS({
|
|
1513
1512
|
...compilation,
|
|
1514
1513
|
properties: [],
|
|
1515
1514
|
keyframes: []
|
|
1516
1515
|
});
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
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(
|
|
1530
|
-
const
|
|
1531
|
-
|
|
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
|
-
|
|
1547
|
+
sections.push(`@layer ${layer.order.join(", ")};`);
|
|
1547
1548
|
}
|
|
1548
|
-
if (
|
|
1549
|
-
const baseJoined =
|
|
1549
|
+
if (parts.base.length > 0) {
|
|
1550
|
+
const baseJoined = parts.base.join("\n\n");
|
|
1550
1551
|
if (layer.base) {
|
|
1551
|
-
|
|
1552
|
+
sections.push(wrapInLayer(baseJoined, layer.base));
|
|
1552
1553
|
} else {
|
|
1553
|
-
|
|
1554
|
+
sections.push(baseJoined);
|
|
1554
1555
|
}
|
|
1555
1556
|
}
|
|
1556
|
-
if (
|
|
1557
|
+
if (parts.utilities !== null) {
|
|
1557
1558
|
if (layer.utilities) {
|
|
1558
|
-
|
|
1559
|
+
sections.push(wrapInLayer(parts.utilities, layer.utilities));
|
|
1559
1560
|
} else {
|
|
1560
|
-
|
|
1561
|
+
sections.push(parts.utilities);
|
|
1561
1562
|
}
|
|
1562
1563
|
}
|
|
1563
|
-
sections
|
|
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
|
-
|
|
1665
|
+
DEFAULT_PATTERNS,
|
|
1666
|
+
DEFAULT_EXCLUDES,
|
|
1667
|
+
finalizeProjectCompilation,
|
|
1668
|
+
compileScannedProject
|
|
1635
1669
|
};
|