@csszyx/unplugin 0.6.2 → 0.7.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/dist/index.cjs CHANGED
@@ -30,11 +30,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ assertNoRSCBoundaryViolation: () => assertNoRSCBoundaryViolation,
34
+ assertNoRSCGraphViolation: () => assertNoRSCGraphViolation,
33
35
  createPostCSSPlugin: () => createPostCSSPlugin,
36
+ createRSCModuleRecord: () => createRSCModuleRecord,
34
37
  default: () => unplugin,
35
38
  esbuildPlugin: () => esbuildPlugin,
36
39
  escapeCSSClassName: () => escapeCSSClassName,
40
+ findRSCBoundaryViolation: () => findRSCBoundaryViolation,
41
+ findRSCGraphViolation: () => findRSCGraphViolation,
37
42
  hasTokens: () => hasTokens,
43
+ hasUseClientDirective: () => hasUseClientDirective,
44
+ hasUseServerDirective: () => hasUseServerDirective,
45
+ isRSCServerModule: () => isRSCServerModule,
38
46
  mangleCSS: () => mangleCSS,
39
47
  mangleCSSSync: () => mangleCSSSync,
40
48
  mangleCodeClassesSync: () => mangleCodeClassesSync,
@@ -243,6 +251,303 @@ function createPostCSSPlugin(mangleMap, options = {}) {
243
251
  };
244
252
  }
245
253
 
254
+ // src/rsc-boundary.ts
255
+ var fs = __toESM(require("fs"), 1);
256
+ var path = __toESM(require("path"), 1);
257
+ var SERVER_DIRECTIVE_RE = /^['"]use server['"];?$/;
258
+ var CLIENT_DIRECTIVE_RE = /^['"]use client['"];?$/;
259
+ var RUNTIME_MODULES = /* @__PURE__ */ new Set([
260
+ "@csszyx/runtime",
261
+ "@csszyx/runtime/lite",
262
+ "csszyx",
263
+ "csszyx/lite"
264
+ ]);
265
+ var FORBIDDEN_SYMBOLS = /* @__PURE__ */ new Set([
266
+ "_sz",
267
+ "_sz2",
268
+ "_sz3",
269
+ "_szIf",
270
+ "_szMerge",
271
+ "_szSwitch",
272
+ "__csszyx_runtime__"
273
+ ]);
274
+ function hasUseServerDirective(code) {
275
+ for (const statement of readDirectivePrologue(code)) {
276
+ if (SERVER_DIRECTIVE_RE.test(statement)) {
277
+ return true;
278
+ }
279
+ if (CLIENT_DIRECTIVE_RE.test(statement)) {
280
+ return false;
281
+ }
282
+ }
283
+ return false;
284
+ }
285
+ function hasUseClientDirective(code) {
286
+ for (const statement of readDirectivePrologue(code)) {
287
+ if (CLIENT_DIRECTIVE_RE.test(statement)) {
288
+ return true;
289
+ }
290
+ if (SERVER_DIRECTIVE_RE.test(statement)) {
291
+ return false;
292
+ }
293
+ }
294
+ return false;
295
+ }
296
+ function isRSCServerModule(code, id) {
297
+ if (hasUseServerDirective(code)) {
298
+ return true;
299
+ }
300
+ if (hasUseClientDirective(code)) {
301
+ return false;
302
+ }
303
+ return isNextAppRouterEntry(id);
304
+ }
305
+ function findRSCBoundaryViolation(code, id) {
306
+ if (!isRSCServerModule(code, id)) {
307
+ return null;
308
+ }
309
+ for (const imported of findRuntimeImports(code)) {
310
+ for (const symbol of imported.symbols) {
311
+ if (FORBIDDEN_SYMBOLS.has(symbol)) {
312
+ return {
313
+ symbol,
314
+ path: id,
315
+ importChain: [id, imported.source]
316
+ };
317
+ }
318
+ }
319
+ }
320
+ return null;
321
+ }
322
+ function createRSCModuleRecord(code, id) {
323
+ const normalized = normalizeModuleId(id);
324
+ return {
325
+ id: normalized,
326
+ isServer: isRSCServerModule(code, normalized),
327
+ isClient: hasUseClientDirective(code),
328
+ imports: findLocalImportSources(code).map((source) => resolveLocalModule(normalized, source)).filter((resolved) => resolved !== null),
329
+ runtimeImports: findRuntimeImports(code).filter((imported) => imported.symbols.some((symbol) => FORBIDDEN_SYMBOLS.has(symbol)))
330
+ };
331
+ }
332
+ function findRSCGraphViolation(records) {
333
+ for (const root of records.values()) {
334
+ if (!root.isServer) {
335
+ continue;
336
+ }
337
+ const violation = walkRSCGraph(root, records, [root.id], /* @__PURE__ */ new Set([root.id]));
338
+ if (violation) {
339
+ return {
340
+ symbol: violation.symbol,
341
+ path: root.id,
342
+ importChain: violation.importChain
343
+ };
344
+ }
345
+ }
346
+ return null;
347
+ }
348
+ function assertNoRSCGraphViolation(records) {
349
+ const violation = findRSCGraphViolation(records);
350
+ if (!violation) {
351
+ return;
352
+ }
353
+ throw new Error(formatRSCViolation(violation));
354
+ }
355
+ function isNextAppRouterEntry(id) {
356
+ const clean = id.split("?")[0]?.replace(/\\/g, "/") ?? id;
357
+ return /(^|\/)app\/.*\/?(?:page|layout|template|loading|error|not-found|global-error|default|route)\.[cm]?[tj]sx?$/.test(clean);
358
+ }
359
+ function assertNoRSCBoundaryViolation(code, id) {
360
+ const violation = findRSCBoundaryViolation(code, id);
361
+ if (!violation) {
362
+ return;
363
+ }
364
+ throw new Error(formatRSCViolation(violation));
365
+ }
366
+ function formatRSCViolation(violation) {
367
+ return `csszyxRSCViolation: ${violation.symbol} imported in Server Component ${violation.path}
368
+ Import chain: ${violation.importChain.join(" -> ")}`;
369
+ }
370
+ function walkRSCGraph(current, records, chain, seen) {
371
+ if (current.isClient) {
372
+ return null;
373
+ }
374
+ const runtime = current.runtimeImports[0];
375
+ const symbol = runtime?.symbols.find((s) => FORBIDDEN_SYMBOLS.has(s));
376
+ if (runtime && symbol) {
377
+ return {
378
+ symbol,
379
+ path: chain[0] ?? current.id,
380
+ importChain: [...chain, runtime.source]
381
+ };
382
+ }
383
+ for (const importedId of current.imports) {
384
+ if (seen.has(importedId)) {
385
+ continue;
386
+ }
387
+ const next = records.get(importedId);
388
+ if (!next) {
389
+ continue;
390
+ }
391
+ seen.add(importedId);
392
+ const violation = walkRSCGraph(next, records, [...chain, importedId], seen);
393
+ if (violation) {
394
+ return violation;
395
+ }
396
+ }
397
+ return null;
398
+ }
399
+ function readDirectivePrologue(code) {
400
+ const out = [];
401
+ let i = code.charCodeAt(0) === 65279 ? 1 : 0;
402
+ while (i < code.length) {
403
+ i = skipWhitespaceAndComments(code, i);
404
+ const quote = code[i];
405
+ if (quote !== '"' && quote !== "'") {
406
+ break;
407
+ }
408
+ let j = i + 1;
409
+ let escaped = false;
410
+ while (j < code.length) {
411
+ const ch = code[j];
412
+ if (escaped) {
413
+ escaped = false;
414
+ } else if (ch === "\\") {
415
+ escaped = true;
416
+ } else if (ch === quote) {
417
+ break;
418
+ }
419
+ j++;
420
+ }
421
+ if (j >= code.length) {
422
+ break;
423
+ }
424
+ let end = j + 1;
425
+ while (end < code.length && /[ \t\r\n]/.test(code[end])) {
426
+ end++;
427
+ }
428
+ if (code[end] === ";") {
429
+ end++;
430
+ }
431
+ out.push(code.slice(i, end).trim());
432
+ i = end;
433
+ }
434
+ return out;
435
+ }
436
+ function skipWhitespaceAndComments(code, start) {
437
+ let i = start;
438
+ while (i < code.length) {
439
+ while (i < code.length && /\s/.test(code[i])) {
440
+ i++;
441
+ }
442
+ if (code.startsWith("//", i)) {
443
+ const next = code.indexOf("\n", i + 2);
444
+ i = next === -1 ? code.length : next + 1;
445
+ continue;
446
+ }
447
+ if (code.startsWith("/*", i)) {
448
+ const next = code.indexOf("*/", i + 2);
449
+ i = next === -1 ? code.length : next + 2;
450
+ continue;
451
+ }
452
+ break;
453
+ }
454
+ return i;
455
+ }
456
+ function findRuntimeImports(code) {
457
+ const imports = [];
458
+ const staticImportRe = /import\s+(?!type\b)([\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
459
+ const sideEffectImportRe = /import\s+['"]([^'"]+)['"]/g;
460
+ const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
461
+ let match;
462
+ while ((match = staticImportRe.exec(code)) !== null) {
463
+ const clause = match[1];
464
+ const source = match[2];
465
+ if (!RUNTIME_MODULES.has(source)) {
466
+ continue;
467
+ }
468
+ imports.push({ source, symbols: readImportedSymbols(clause) });
469
+ }
470
+ while ((match = sideEffectImportRe.exec(code)) !== null) {
471
+ const source = match[1];
472
+ if (RUNTIME_MODULES.has(source)) {
473
+ imports.push({ source, symbols: [] });
474
+ }
475
+ }
476
+ while ((match = dynamicImportRe.exec(code)) !== null) {
477
+ const source = match[1];
478
+ if (RUNTIME_MODULES.has(source)) {
479
+ imports.push({ source, symbols: Array.from(FORBIDDEN_SYMBOLS) });
480
+ }
481
+ }
482
+ return imports;
483
+ }
484
+ function findLocalImportSources(code) {
485
+ const out = [];
486
+ const staticImportRe = /import\s+(?!type\b)(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
487
+ const exportFromRe = /export\s+(?!type\b)(?:[\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
488
+ const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
489
+ let match;
490
+ for (const re of [staticImportRe, exportFromRe, dynamicImportRe]) {
491
+ while ((match = re.exec(code)) !== null) {
492
+ const source = match[1];
493
+ if (source.startsWith(".") || source.startsWith("/")) {
494
+ out.push(source);
495
+ }
496
+ }
497
+ }
498
+ return out;
499
+ }
500
+ function normalizeModuleId(id) {
501
+ const clean = id.split("?")[0] ?? id;
502
+ try {
503
+ return fs.realpathSync.native(clean).replace(/\\/g, "/");
504
+ } catch {
505
+ return path.resolve(clean).replace(/\\/g, "/");
506
+ }
507
+ }
508
+ function resolveLocalModule(importer, source) {
509
+ const base = source.startsWith("/") ? source : path.resolve(path.dirname(importer), source);
510
+ const candidates = [
511
+ base,
512
+ `${base}.tsx`,
513
+ `${base}.ts`,
514
+ `${base}.jsx`,
515
+ `${base}.js`,
516
+ `${base}.mjs`,
517
+ `${base}.cjs`,
518
+ path.join(base, "index.tsx"),
519
+ path.join(base, "index.ts"),
520
+ path.join(base, "index.jsx"),
521
+ path.join(base, "index.js")
522
+ ];
523
+ for (const candidate of candidates) {
524
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
525
+ return normalizeModuleId(candidate);
526
+ }
527
+ }
528
+ return null;
529
+ }
530
+ function readImportedSymbols(clause) {
531
+ const symbols = [];
532
+ const named = clause.match(/\{([\s\S]*?)\}/);
533
+ if (named) {
534
+ for (const part of named[1].split(",")) {
535
+ const trimmed = part.trim();
536
+ if (!trimmed || trimmed.startsWith("type ")) {
537
+ continue;
538
+ }
539
+ const sourceName = trimmed.replace(/^type\s+/, "").split(/\s+as\s+/)[0]?.trim();
540
+ if (sourceName) {
541
+ symbols.push(sourceName);
542
+ }
543
+ }
544
+ }
545
+ if (/\*\s+as\s+\w+/.test(clause)) {
546
+ symbols.push(...FORBIDDEN_SYMBOLS);
547
+ }
548
+ return symbols;
549
+ }
550
+
246
551
  // src/theme-scanner.ts
247
552
  var EMPTY_THEME = { colors: [], spacings: [], fonts: [], radii: [], shadows: [] };
248
553
  function stripLayerWrappers(css) {
@@ -386,8 +691,8 @@ function hasTokens(theme) {
386
691
  }
387
692
 
388
693
  // src/unplugin.ts
389
- var fs = __toESM(require("fs"), 1);
390
- var path = __toESM(require("path"), 1);
694
+ var fs2 = __toESM(require("fs"), 1);
695
+ var path2 = __toESM(require("path"), 1);
391
696
  var import_compiler = require("@csszyx/compiler");
392
697
  var import_core = require("@csszyx/core");
393
698
  var import_svelte_adapter = require("@csszyx/svelte-adapter");
@@ -455,7 +760,7 @@ function injectHydrationData(html, mangleMap, checksum, options = {}) {
455
760
  function transformIndexHtml(html, mangleMap, checksum, options = {}) {
456
761
  return injectHydrationData(html, mangleMap, checksum, options);
457
762
  }
458
- function buildRecoveryManifest(tokens, options = {}) {
763
+ function buildRecoveryManifest(tokens, options) {
459
764
  const stripped = options.production === true;
460
765
  const strippedDevOnlyPaths = [];
461
766
  const sorted = {};
@@ -476,7 +781,7 @@ function buildRecoveryManifest(tokens, options = {}) {
476
781
  const checksum = fullChecksum.substring(0, 16);
477
782
  const buildId = `${Date.now().toString(36)}-${fullChecksum.substring(0, 6)}`;
478
783
  return {
479
- manifest: { buildId, checksum, tokens: sorted },
784
+ manifest: { buildId, checksum, mangleChecksum: options.mangleChecksum, tokens: sorted },
480
785
  strippedDevOnlyPaths
481
786
  };
482
787
  }
@@ -605,8 +910,8 @@ function runThemeScan(rootDir, scanCss) {
605
910
  const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
606
911
  const sourceFiles = [];
607
912
  for (const pattern of patterns) {
608
- const resolved = path.isAbsolute(pattern) ? pattern : path.join(rootDir, pattern);
609
- if (fs.existsSync(resolved)) {
913
+ const resolved = path2.isAbsolute(pattern) ? pattern : path2.join(rootDir, pattern);
914
+ if (fs2.existsSync(resolved)) {
610
915
  sourceFiles.push(resolved);
611
916
  }
612
917
  }
@@ -615,20 +920,20 @@ function runThemeScan(rootDir, scanCss) {
615
920
  }
616
921
  const themes = sourceFiles.map((f) => {
617
922
  try {
618
- return parseThemeBlocks(fs.readFileSync(f, "utf-8"));
923
+ return parseThemeBlocks(fs2.readFileSync(f, "utf-8"));
619
924
  } catch {
620
925
  return null;
621
926
  }
622
927
  }).filter((t) => t !== null);
623
928
  const merged = mergeThemes(themes);
624
- const outputPath = path.join(rootDir, ".csszyx", "theme.d.ts");
929
+ const outputPath = path2.join(rootDir, ".csszyx", "theme.d.ts");
625
930
  writeThemeDts({ outputPath, theme: merged, sourceFiles });
626
931
  if (!_hasWarnedTsConfig) {
627
932
  _hasWarnedTsConfig = true;
628
933
  try {
629
934
  const checkFile = (cfgPath) => {
630
- if (fs.existsSync(cfgPath)) {
631
- const content = fs.readFileSync(cfgPath, "utf-8");
935
+ if (fs2.existsSync(cfgPath)) {
936
+ const content = fs2.readFileSync(cfgPath, "utf-8");
632
937
  if (!content.includes(".csszyx")) {
633
938
  console.warn(`
634
939
  \x1B[33m\u26A0\uFE0F CSSzyx: Theme Auto-Scan enabled, but TypeScript isn't configured. Run "npx @csszyx/cli init" to fix.\x1B[0m
@@ -638,8 +943,8 @@ function runThemeScan(rootDir, scanCss) {
638
943
  }
639
944
  return false;
640
945
  };
641
- if (!checkFile(path.join(rootDir, "tsconfig.json"))) {
642
- checkFile(path.join(rootDir, "tsconfig.app.json"));
946
+ if (!checkFile(path2.join(rootDir, "tsconfig.json"))) {
947
+ checkFile(path2.join(rootDir, "tsconfig.app.json"));
643
948
  }
644
949
  } catch {
645
950
  }
@@ -826,7 +1131,8 @@ function createCsszyxPlugins(options = {}) {
826
1131
  checksum: "",
827
1132
  finalized: false,
828
1133
  rootDir: process.cwd(),
829
- recoveryTokens: /* @__PURE__ */ new Map()
1134
+ recoveryTokens: /* @__PURE__ */ new Map(),
1135
+ rscModules: /* @__PURE__ */ new Map()
830
1136
  };
831
1137
  const SAFELIST_FILENAME = "csszyx-classes.html";
832
1138
  const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js"]);
@@ -835,16 +1141,16 @@ function createCsszyxPlugins(options = {}) {
835
1141
  if (classes.size === 0) {
836
1142
  return;
837
1143
  }
838
- const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME);
1144
+ const safelistPath = path2.join(state.rootDir, SAFELIST_FILENAME);
839
1145
  const classList = Array.from(classes).join(" ");
840
1146
  const content = `<!-- Auto-generated by csszyx \u2014 DO NOT EDIT -->
841
1147
  <!-- Tailwind CSS scans this file for class name detection -->
842
1148
  <div class="${classList}"><div class="${classList}">x</div><div class="${classList}">x</div></div>
843
1149
  `;
844
1150
  try {
845
- const existing = fs.existsSync(safelistPath) ? fs.readFileSync(safelistPath, "utf-8") : "";
1151
+ const existing = fs2.existsSync(safelistPath) ? fs2.readFileSync(safelistPath, "utf-8") : "";
846
1152
  if (existing !== content) {
847
- fs.writeFileSync(safelistPath, content);
1153
+ fs2.writeFileSync(safelistPath, content);
848
1154
  }
849
1155
  } catch {
850
1156
  }
@@ -855,19 +1161,19 @@ function createCsszyxPlugins(options = {}) {
855
1161
  function scanDir(dir) {
856
1162
  let entries;
857
1163
  try {
858
- entries = fs.readdirSync(dir, { withFileTypes: true });
1164
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
859
1165
  } catch {
860
1166
  return;
861
1167
  }
862
1168
  for (const entry of entries) {
863
1169
  if (entry.isDirectory()) {
864
1170
  if (!IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
865
- scanDir(path.join(dir, entry.name));
1171
+ scanDir(path2.join(dir, entry.name));
866
1172
  }
867
- } else if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
868
- const filePath = path.join(dir, entry.name);
1173
+ } else if (SOURCE_EXTENSIONS.has(path2.extname(entry.name))) {
1174
+ const filePath = path2.join(dir, entry.name);
869
1175
  try {
870
- const content = fs.readFileSync(filePath, "utf-8");
1176
+ const content = fs2.readFileSync(filePath, "utf-8");
871
1177
  if (!content.includes("sz=") && !content.includes("sz:")) {
872
1178
  continue;
873
1179
  }
@@ -1059,14 +1365,17 @@ function createCsszyxPlugins(options = {}) {
1059
1365
  * @returns transformed code with source map, or null if no changes were made
1060
1366
  */
1061
1367
  transform(code, id) {
1368
+ if (/\.[tj]sx?(\?.*)?$/.test(id)) {
1369
+ assertNoRSCBoundaryViolation(code, id);
1370
+ }
1062
1371
  if (/\.css(\?.*)?$/.test(id)) {
1063
1372
  const hasTailwindImport = code.includes('@import "tailwindcss') || code.includes("@import 'tailwindcss");
1064
1373
  if (hasTailwindImport && state.classes.size > 0) {
1065
1374
  const candidates = Array.from(state.classes).filter((c) => c.length >= 2 && /^[a-z]/.test(c)).join(" ");
1066
1375
  if (candidates) {
1067
- const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME).replace(/\\/g, "/");
1068
- const cssDir = path.dirname(id).replace(/\\/g, "/");
1069
- let relPath = path.posix.relative(cssDir, safelistPath);
1376
+ const safelistPath = path2.join(state.rootDir, SAFELIST_FILENAME).replace(/\\/g, "/");
1377
+ const cssDir = path2.dirname(id).replace(/\\/g, "/");
1378
+ let relPath = path2.posix.relative(cssDir, safelistPath);
1070
1379
  if (!relPath.startsWith(".")) {
1071
1380
  relPath = "./" + relPath;
1072
1381
  }
@@ -1170,6 +1479,11 @@ ${sourceDirective}`
1170
1479
  transformed = true;
1171
1480
  }
1172
1481
  }
1482
+ if (/\.[tj]sx?(\?.*)?$/.test(id)) {
1483
+ assertNoRSCBoundaryViolation(transformedCode, id);
1484
+ const record = createRSCModuleRecord(transformedCode, id);
1485
+ state.rscModules.set(record.id, record);
1486
+ }
1173
1487
  if (transformed || transformedCode.includes("class=") || transformedCode.includes("className=")) {
1174
1488
  if (szClasses !== void 0) {
1175
1489
  for (const cls of szClasses) {
@@ -1185,6 +1499,7 @@ ${sourceDirective}`
1185
1499
  /** Finalizes the mangle map after all source modules have been processed. */
1186
1500
  buildEnd() {
1187
1501
  finalizeMangleMap();
1502
+ assertNoRSCGraphViolation(state.rscModules);
1188
1503
  if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
1189
1504
  globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
1190
1505
  }
@@ -1207,8 +1522,8 @@ ${sourceDirective}`
1207
1522
  compiler.hooks.thisCompilation.tap("csszyx:theme-deps", (compilation) => {
1208
1523
  const root = compiler.context || process.cwd();
1209
1524
  for (const pattern of patterns) {
1210
- const resolved = path.isAbsolute(pattern) ? pattern : path.join(root, pattern);
1211
- if (fs.existsSync(resolved)) {
1525
+ const resolved = path2.isAbsolute(pattern) ? pattern : path2.join(root, pattern);
1526
+ if (fs2.existsSync(resolved)) {
1212
1527
  compilation.fileDependencies.add(resolved);
1213
1528
  }
1214
1529
  }
@@ -1238,14 +1553,14 @@ ${sourceDirective}`
1238
1553
  const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
1239
1554
  const root = ctx.server.config.root || process.cwd();
1240
1555
  const isWatched = patterns.some((p) => {
1241
- const resolved = path.isAbsolute(p) ? p : path.join(root, p);
1556
+ const resolved = path2.isAbsolute(p) ? p : path2.join(root, p);
1242
1557
  return ctx.file === resolved;
1243
1558
  });
1244
1559
  if (isWatched) {
1245
1560
  runThemeScan(root, scanCss);
1246
1561
  }
1247
1562
  }
1248
- if (!SOURCE_EXTENSIONS.has(path.extname(ctx.file))) {
1563
+ if (!SOURCE_EXTENSIONS.has(path2.extname(ctx.file))) {
1249
1564
  return;
1250
1565
  }
1251
1566
  if (ctx.file.includes("node_modules")) {
@@ -1253,7 +1568,7 @@ ${sourceDirective}`
1253
1568
  }
1254
1569
  let fileContent, result;
1255
1570
  try {
1256
- fileContent = fs.readFileSync(ctx.file, "utf-8");
1571
+ fileContent = fs2.readFileSync(ctx.file, "utf-8");
1257
1572
  } catch {
1258
1573
  return;
1259
1574
  }
@@ -1277,7 +1592,7 @@ ${sourceDirective}`
1277
1592
  }
1278
1593
  if (state.classes.size > sizeBefore) {
1279
1594
  writeSafelistFile(state.classes);
1280
- const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME);
1595
+ const safelistPath = path2.join(state.rootDir, SAFELIST_FILENAME);
1281
1596
  ctx.server.watcher.emit("change", safelistPath);
1282
1597
  }
1283
1598
  },
@@ -1299,7 +1614,10 @@ ${sourceDirective}`
1299
1614
  const isProduction = process.env.NODE_ENV === "production";
1300
1615
  const { manifest, strippedDevOnlyPaths } = buildRecoveryManifest(
1301
1616
  state.recoveryTokens,
1302
- { production: isProduction }
1617
+ {
1618
+ production: isProduction,
1619
+ mangleChecksum: state.checksum
1620
+ }
1303
1621
  );
1304
1622
  if (strippedDevOnlyPaths.length > 0) {
1305
1623
  console.warn(
@@ -1513,10 +1831,18 @@ var esbuildPlugin = (options = {}) => {
1513
1831
  };
1514
1832
  // Annotate the CommonJS export names for ESM import in node:
1515
1833
  0 && (module.exports = {
1834
+ assertNoRSCBoundaryViolation,
1835
+ assertNoRSCGraphViolation,
1516
1836
  createPostCSSPlugin,
1837
+ createRSCModuleRecord,
1517
1838
  esbuildPlugin,
1518
1839
  escapeCSSClassName,
1840
+ findRSCBoundaryViolation,
1841
+ findRSCGraphViolation,
1519
1842
  hasTokens,
1843
+ hasUseClientDirective,
1844
+ hasUseServerDirective,
1845
+ isRSCServerModule,
1520
1846
  mangleCSS,
1521
1847
  mangleCSSSync,
1522
1848
  mangleCodeClassesSync,
package/dist/index.d.cts CHANGED
@@ -7,6 +7,101 @@ import 'rollup';
7
7
  import 'unplugin';
8
8
  import 'vite';
9
9
 
10
+ /**
11
+ * Direct RSC boundary violation found in a transformed module.
12
+ */
13
+ interface RSCBoundaryViolation {
14
+ /** Forbidden runtime helper that crossed into an RSC server module. */
15
+ symbol: string;
16
+ /** Server module path where the import was found. */
17
+ path: string;
18
+ /** Import chain used in the fatal build error. */
19
+ importChain: string[];
20
+ }
21
+ /**
22
+ * RSC module metadata collected during the transform phase.
23
+ */
24
+ interface RSCModuleRecord {
25
+ /** Normalized absolute module ID. */
26
+ id: string;
27
+ /** True when this module is an RSC server module entry or has `'use server'`. */
28
+ isServer: boolean;
29
+ /** True when this module declares the client boundary. */
30
+ isClient: boolean;
31
+ /** Local modules imported by this file after path resolution. */
32
+ imports: string[];
33
+ /** Forbidden runtime imports found directly in this module. */
34
+ runtimeImports: Array<{
35
+ source: string;
36
+ symbols: string[];
37
+ }>;
38
+ }
39
+ /**
40
+ * Returns true when a module starts with the top-level `'use server'`
41
+ * directive. Comments and blank lines before the directive are allowed, but
42
+ * detection stops at the first real statement.
43
+ *
44
+ * @param code module source
45
+ * @returns true when the module has a top-level `'use server'` directive
46
+ */
47
+ declare function hasUseServerDirective(code: string): boolean;
48
+ /**
49
+ * Returns true when a module starts with the top-level `'use client'`
50
+ * directive.
51
+ *
52
+ * @param code module source
53
+ * @returns true when the module has a top-level `'use client'` directive
54
+ */
55
+ declare function hasUseClientDirective(code: string): boolean;
56
+ /**
57
+ * Detects modules that should be treated as RSC server modules by csszyx.
58
+ *
59
+ * @param code module source
60
+ * @param id module ID/path
61
+ * @returns true when the module is server-side for RSC boundary purposes
62
+ */
63
+ declare function isRSCServerModule(code: string, id: string): boolean;
64
+ /**
65
+ * Finds the first direct forbidden runtime helper import in an RSC server
66
+ * module.
67
+ *
68
+ * @param code module source
69
+ * @param id module ID/path
70
+ * @returns violation details, or null when the module is allowed
71
+ */
72
+ declare function findRSCBoundaryViolation(code: string, id: string): RSCBoundaryViolation | null;
73
+ /**
74
+ * Builds module metadata for the RSC graph walker.
75
+ *
76
+ * @param code module source
77
+ * @param id module ID/path
78
+ * @returns graph metadata for the module
79
+ */
80
+ declare function createRSCModuleRecord(code: string, id: string): RSCModuleRecord;
81
+ /**
82
+ * Finds forbidden runtime helper imports reachable from an RSC server module.
83
+ * Traversal stops at `'use client'` modules because they define a separate
84
+ * client module graph.
85
+ *
86
+ * @param records module graph records keyed by normalized module ID
87
+ * @returns first graph violation, or null when the graph is allowed
88
+ */
89
+ declare function findRSCGraphViolation(records: Map<string, RSCModuleRecord>): RSCBoundaryViolation | null;
90
+ /**
91
+ * Throws the spec-format fatal RSC boundary error for graph-level violations.
92
+ *
93
+ * @param records module graph records keyed by normalized module ID
94
+ */
95
+ declare function assertNoRSCGraphViolation(records: Map<string, RSCModuleRecord>): void;
96
+ /**
97
+ * Throws the spec-format fatal RSC boundary error when a server module imports
98
+ * a forbidden csszyx runtime helper.
99
+ *
100
+ * @param code module source
101
+ * @param id module ID/path
102
+ */
103
+ declare function assertNoRSCBoundaryViolation(code: string, id: string): void;
104
+
10
105
  /**
11
106
  * Theme Scanner — parses Tailwind v4 @theme blocks from CSS files.
12
107
  *
@@ -55,4 +150,4 @@ declare function mergeThemes(themes: ParsedTheme[]): ParsedTheme;
55
150
  */
56
151
  declare function hasTokens(theme: ParsedTheme): boolean;
57
152
 
58
- export { type ParsedTheme, hasTokens, mergeThemes, parseThemeBlocks };
153
+ export { type ParsedTheme, type RSCBoundaryViolation, type RSCModuleRecord, assertNoRSCBoundaryViolation, assertNoRSCGraphViolation, createRSCModuleRecord, findRSCBoundaryViolation, findRSCGraphViolation, hasTokens, hasUseClientDirective, hasUseServerDirective, isRSCServerModule, mergeThemes, parseThemeBlocks };