@csszyx/unplugin 0.14.4 → 0.15.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.
Files changed (52) hide show
  1. package/README.md +4 -3
  2. package/dist/index.cjs +8 -7
  3. package/dist/index.d.cts +70 -3
  4. package/dist/index.d.mts +70 -3
  5. package/dist/index.mjs +4 -4
  6. package/dist/next-config.d.cts +1 -2
  7. package/dist/next-config.d.mts +1 -2
  8. package/dist/next-prebuild.cjs +9 -5
  9. package/dist/next-prebuild.d.cts +4 -2
  10. package/dist/next-prebuild.d.mts +4 -2
  11. package/dist/next-prebuild.mjs +8 -4
  12. package/dist/next-turbo-loader.cjs +20 -14
  13. package/dist/next-turbo-loader.d.cts +1 -1
  14. package/dist/next-turbo-loader.d.mts +1 -1
  15. package/dist/next-turbo-loader.mjs +19 -13
  16. package/dist/next-watcher.cjs +2 -2
  17. package/dist/next-watcher.d.cts +2 -2
  18. package/dist/next-watcher.d.mts +2 -2
  19. package/dist/next-watcher.mjs +2 -2
  20. package/dist/postcss.cjs +124 -0
  21. package/dist/postcss.d.cts +42 -0
  22. package/dist/postcss.d.mts +42 -0
  23. package/dist/postcss.mjs +107 -0
  24. package/dist/shared/{unplugin.NlPvn5P4.mjs → unplugin.AGR7mihU.mjs} +2 -6
  25. package/dist/shared/{unplugin.BnRf0Tvd.mjs → unplugin.BGufkSFT.mjs} +297 -173
  26. package/dist/shared/{unplugin.BXYakiXt.d.cts → unplugin.BHfqCocB.d.cts} +53 -55
  27. package/dist/shared/{unplugin.BXYakiXt.d.mts → unplugin.BHfqCocB.d.mts} +53 -55
  28. package/dist/shared/{unplugin.RGzhYt-D.cjs → unplugin.BLbrShMZ.cjs} +344 -221
  29. package/dist/shared/{unplugin.DVbPNK3W.cjs → unplugin.BbBYWFGT.cjs} +6 -6
  30. package/dist/shared/{unplugin.BZPdvVcj.mjs → unplugin.BourSpnZ.mjs} +105 -127
  31. package/dist/shared/{unplugin.CPEWNSA0.d.cts → unplugin.C2ilavL1.d.cts} +2 -0
  32. package/dist/shared/{unplugin.CPEWNSA0.d.mts → unplugin.C2ilavL1.d.mts} +2 -0
  33. package/dist/shared/{unplugin.D8BxOOkA.cjs → unplugin.C7i0agiO.cjs} +121 -142
  34. package/dist/shared/{unplugin.CdZxp0x-.d.mts → unplugin.CXCaQ3-s.d.cts} +1 -1
  35. package/dist/shared/{unplugin.xeED_qwh.d.cts → unplugin.CbNydVwv.d.mts} +1 -1
  36. package/dist/shared/unplugin.CqJS0BbD.mjs +211 -0
  37. package/dist/shared/{unplugin.VtXstzEh.cjs → unplugin.DX3rYT4P.cjs} +6 -11
  38. package/dist/shared/{unplugin.Cji6O5jv.cjs → unplugin.Dai5wlZ3.cjs} +27 -19
  39. package/dist/shared/{unplugin.DN95k991.mjs → unplugin.Dbc26ty6.mjs} +2 -2
  40. package/dist/shared/{unplugin.BU0O4IkX.mjs → unplugin.DmCOQc-b.mjs} +22 -16
  41. package/dist/shared/unplugin.KnDCM-sr.cjs +241 -0
  42. package/dist/vite.cjs +4 -4
  43. package/dist/vite.d.cts +2 -2
  44. package/dist/vite.d.mts +1 -1
  45. package/dist/vite.mjs +4 -4
  46. package/dist/webpack.cjs +4 -4
  47. package/dist/webpack.d.cts +2 -1
  48. package/dist/webpack.d.mts +1 -1
  49. package/dist/webpack.mjs +4 -4
  50. package/package.json +19 -9
  51. package/dist/shared/unplugin.B3XoSRB8.mjs +0 -40
  52. package/dist/shared/unplugin.BkRah5Ot.cjs +0 -48
@@ -0,0 +1,211 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ function sortStrings(values) {
5
+ return [...values].sort((a, b) => {
6
+ if (a < b) return -1;
7
+ if (a > b) return 1;
8
+ return 0;
9
+ });
10
+ }
11
+
12
+ const WINDOWS_PATH_SEPARATOR = String.fromCodePoint(92);
13
+ function normalizePathSeparators(value) {
14
+ return value.split(WINDOWS_PATH_SEPARATOR).join("/");
15
+ }
16
+
17
+ const SAFELIST_HEADER = "# Auto-generated by csszyx \u2014 DO NOT EDIT\n# Tailwind scans this file for class names, one per line.\n";
18
+ function renderSafelistFile(classNames) {
19
+ if (classNames.length === 0) {
20
+ return SAFELIST_HEADER;
21
+ }
22
+ return `${SAFELIST_HEADER}${classNames.join("\n")}
23
+ `;
24
+ }
25
+
26
+ const SAFELIST_FILE = ".csszyx/csszyx-classes.txt";
27
+ const LEGACY_SAFELIST_FILES = [
28
+ "csszyx-classes.html",
29
+ ".csszyx/next-loader-classes.html"
30
+ ];
31
+ const LEGACY_HTML_MARKERS = [
32
+ "<!-- Auto-generated by csszyx",
33
+ "<!-- csszyx Next safelist",
34
+ "<!-- csszyx exact scanner candidates -->"
35
+ ];
36
+ function removeLegacySafelists(rootDir, warn) {
37
+ for (const legacy of LEGACY_SAFELIST_FILES) {
38
+ const file = path.join(rootDir, legacy);
39
+ let content;
40
+ try {
41
+ content = fs.readFileSync(file, "utf8");
42
+ } catch {
43
+ continue;
44
+ }
45
+ if (!LEGACY_HTML_MARKERS.some((marker) => content.includes(marker))) continue;
46
+ try {
47
+ fs.rmSync(file, { force: true });
48
+ } catch (error) {
49
+ const { message } = error;
50
+ warn(`[csszyx] could not remove ${legacy}: ${message}. Delete it by hand.`);
51
+ continue;
52
+ }
53
+ warn(
54
+ `[csszyx] removed ${legacy}: the safelist now lives at ${SAFELIST_FILE}. Update any hand-written @source that named the old file, or drop it and list '@csszyx/unplugin/postcss' before '@tailwindcss/postcss' in postcss.config.`
55
+ );
56
+ }
57
+ }
58
+ const LEGACY_SAFELIST_BASENAMES = new Set(
59
+ LEGACY_SAFELIST_FILES.map((file) => path.posix.basename(file))
60
+ );
61
+ function isDanglingLegacySource(target, cssFile) {
62
+ if (!LEGACY_SAFELIST_BASENAMES.has(path.posix.basename(target))) return false;
63
+ return !fs.existsSync(path.resolve(path.dirname(cssFile), target));
64
+ }
65
+ function findLegacySourceDirective(code, cssFile) {
66
+ const withoutComments = stripCssBlockComments(code);
67
+ for (const match of withoutComments.matchAll(/@source\s+(["'])([^"']+)\1/g)) {
68
+ const target = match[2];
69
+ if (isDanglingLegacySource(target, cssFile)) {
70
+ return { directive: match[0], target };
71
+ }
72
+ }
73
+ return null;
74
+ }
75
+ const SKIPPED_DIRS = /* @__PURE__ */ new Set([
76
+ "node_modules",
77
+ ".git",
78
+ ".csszyx",
79
+ "dist",
80
+ "build",
81
+ "out",
82
+ "coverage",
83
+ ".turbo",
84
+ ".vercel"
85
+ ]);
86
+ const STYLESHEET_EXTENSIONS = /* @__PURE__ */ new Set([".css", ".pcss", ".postcss"]);
87
+ function findLegacySourceStylesheet(rootDir) {
88
+ const pending = [rootDir];
89
+ while (pending.length > 0) {
90
+ const dir = pending.pop();
91
+ for (const entry of readDirectory(dir)) {
92
+ if (entry.isDirectory()) {
93
+ if (!isSkippedDirectory(entry.name)) pending.push(path.join(dir, entry.name));
94
+ continue;
95
+ }
96
+ const found = readLegacySourceDirective(dir, entry);
97
+ if (found !== null) return found;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ function readDirectory(dir) {
103
+ try {
104
+ return fs.readdirSync(dir, { withFileTypes: true });
105
+ } catch {
106
+ return [];
107
+ }
108
+ }
109
+ function isSkippedDirectory(name) {
110
+ return SKIPPED_DIRS.has(name) || name.startsWith(".next");
111
+ }
112
+ function readLegacySourceDirective(dir, entry) {
113
+ if (!entry.isFile() || !STYLESHEET_EXTENSIONS.has(path.extname(entry.name))) return null;
114
+ const file = path.join(dir, entry.name);
115
+ const code = fs.readFileSync(file, "utf8");
116
+ if (!code.includes("@source")) return null;
117
+ const found = findLegacySourceDirective(code, file);
118
+ return found === null ? null : { file, ...found };
119
+ }
120
+ const ROOTS_WITHOUT_LEGACY_SOURCE = /* @__PURE__ */ new Set();
121
+ function assertNoLegacySourceStylesheet(rootDir) {
122
+ if (ROOTS_WITHOUT_LEGACY_SOURCE.has(rootDir)) return;
123
+ const found = findLegacySourceStylesheet(rootDir);
124
+ if (found !== null) throw new Error(legacySourceMessage(found.file, found.target));
125
+ ROOTS_WITHOUT_LEGACY_SOURCE.add(rootDir);
126
+ }
127
+ const CSSZYX_POSTCSS_PLUGIN = "@csszyx/unplugin/postcss";
128
+ const POSTCSS_CONFIG_FILES = [
129
+ ".postcssrc.json",
130
+ "postcss.config.json",
131
+ ".postcssrc.js",
132
+ "postcss.config.js",
133
+ "postcss.config.mjs",
134
+ "postcss.config.cjs"
135
+ ];
136
+ function findPostcssConfigWithoutCsszyx(rootDir) {
137
+ const packageJson = path.join(rootDir, "package.json");
138
+ let postcssKey;
139
+ try {
140
+ postcssKey = JSON.parse(fs.readFileSync(packageJson, "utf8")).postcss;
141
+ } catch {
142
+ postcssKey = void 0;
143
+ }
144
+ if (postcssKey !== void 0) {
145
+ return JSON.stringify(postcssKey).includes(CSSZYX_POSTCSS_PLUGIN) ? null : packageJson;
146
+ }
147
+ for (const name of POSTCSS_CONFIG_FILES) {
148
+ const file = path.join(rootDir, name);
149
+ let text;
150
+ try {
151
+ text = fs.readFileSync(file, "utf8");
152
+ } catch {
153
+ continue;
154
+ }
155
+ return text.includes(CSSZYX_POSTCSS_PLUGIN) ? null : file;
156
+ }
157
+ return null;
158
+ }
159
+ function missingPostcssPluginMessage(configFile) {
160
+ return `[csszyx] ${configFile} does not list '@csszyx/unplugin/postcss'. Next.js reads the safelist only through that plugin: list it before '@tailwindcss/postcss', or no sz class gets CSS.`;
161
+ }
162
+ function legacySourceMessage(cssFile, target) {
163
+ return `[csszyx] ${cssFile}: @source "${target}" names the old safelist, which csszyx no longer writes. The safelist is now ${SAFELIST_FILE}, and csszyx adds the directive itself: remove this line. Vite, Rollup and webpack inject it; on Next.js list '@csszyx/unplugin/postcss' before '@tailwindcss/postcss' in postcss.config.`;
164
+ }
165
+ const TAILWIND_SPECIFIER = String.raw`["']tailwindcss(?:\/[^"']*)?["']`;
166
+ const TAILWIND_IMPORT = new RegExp(String.raw`@import\s+${TAILWIND_SPECIFIER}`);
167
+ function computeSafelistRelPath(rootDir, safelistFilename, cssId) {
168
+ const safelistPath = normalizePathSeparators(path.join(rootDir, safelistFilename));
169
+ const cssDir = normalizePathSeparators(path.dirname(cssId));
170
+ let relPath = path.posix.relative(cssDir, safelistPath);
171
+ if (!relPath.startsWith("./") && !relPath.startsWith("../")) {
172
+ relPath = `./${relPath}`;
173
+ }
174
+ return relPath;
175
+ }
176
+ function appendTailwindSourceDirective(code, relPath) {
177
+ const directive = `@source "${relPath}";`;
178
+ if (code.includes(directive)) {
179
+ return null;
180
+ }
181
+ const separator = code.length === 0 || code.endsWith("\n") ? "" : "\n";
182
+ return `${code}${separator}${directive}
183
+ `;
184
+ }
185
+ function stripCssBlockComments(code) {
186
+ const SLASH = 47;
187
+ const STAR = 42;
188
+ let out = "";
189
+ let last = 0;
190
+ let i = 0;
191
+ const n = code.length;
192
+ while (i < n) {
193
+ if (code.codePointAt(i) === SLASH && code.codePointAt(i + 1) === STAR) {
194
+ out += code.slice(last, i);
195
+ i += 2;
196
+ while (i < n && !(code.codePointAt(i) === STAR && code.codePointAt(i + 1) === SLASH)) {
197
+ i++;
198
+ }
199
+ i += 2;
200
+ last = i;
201
+ } else {
202
+ i++;
203
+ }
204
+ }
205
+ return out + code.slice(last);
206
+ }
207
+ function cssImportsTailwind(code) {
208
+ return TAILWIND_IMPORT.test(stripCssBlockComments(code));
209
+ }
210
+
211
+ export { SAFELIST_FILE as S, appendTailwindSourceDirective as a, cssImportsTailwind as b, computeSafelistRelPath as c, removeLegacySafelists as d, assertNoLegacySourceStylesheet as e, findPostcssConfigWithoutCsszyx as f, stripCssBlockComments as g, findLegacySourceDirective as h, legacySourceMessage as l, missingPostcssPluginMessage as m, normalizePathSeparators as n, renderSafelistFile as r, sortStrings as s };
@@ -2,6 +2,7 @@
2
2
 
3
3
  const path = require('node:path');
4
4
  const compiler = require('@csszyx/compiler');
5
+ const safelistSource = require('./unplugin.KnDCM-sr.cjs');
5
6
  const fs = require('node:fs');
6
7
  const node_crypto = require('node:crypto');
7
8
 
@@ -20,11 +21,6 @@ function _interopNamespaceCompat(e) {
20
21
  const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
21
22
  const fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
22
23
 
23
- const WINDOWS_PATH_SEPARATOR = String.fromCodePoint(92);
24
- function normalizePathSeparators(value) {
25
- return value.split(WINDOWS_PATH_SEPARATOR).join("/");
26
- }
27
-
28
24
  const TSCONFIG_CANDIDATES = ["tsconfig.json", "jsconfig.json", "tsconfig.app.json"];
29
25
  const MAX_EXTENDS_DEPTH = 8;
30
26
  function collectSpecifierAliases(rootDir, resolveAlias) {
@@ -115,12 +111,12 @@ function aliasedSpecifierBases(specifier, aliases) {
115
111
  continue;
116
112
  }
117
113
  if (!specifier.startsWith(alias.find)) continue;
118
- bases.push(normalizePathSeparators(alias.replacement + specifier.slice(alias.find.length)));
114
+ bases.push(safelistSource.normalizePathSeparators(alias.replacement + specifier.slice(alias.find.length)));
119
115
  }
120
116
  return bases;
121
117
  }
122
118
  function absolute(directory, target) {
123
- const resolved = normalizePathSeparators(path__namespace.resolve(directory, target));
119
+ const resolved = safelistSource.normalizePathSeparators(path__namespace.resolve(directory, target));
124
120
  const declaredTrailingSlash = target.endsWith("/") || target.endsWith("\\");
125
121
  return declaredTrailingSlash && !resolved.endsWith("/") ? `${resolved}/` : resolved;
126
122
  }
@@ -189,7 +185,7 @@ function recordSzObjectRegistryFile(registry, filePath, content) {
189
185
  );
190
186
  }
191
187
  function replaceEntriesOfKind(registry, filePath, kind, entries) {
192
- const key = normalizePathSeparators(filePath);
188
+ const key = safelistSource.normalizePathSeparators(filePath);
193
189
  const byName = emptyNameIndex();
194
190
  for (const [name, recorded] of Object.entries(registry.get(key) ?? {})) {
195
191
  if (recorded.kind !== kind) byName[name] = recorded;
@@ -211,7 +207,7 @@ const EMITTED_EXTENSION_SOURCES = [
211
207
  [".cjs", [".cts"]]
212
208
  ];
213
209
  function recordCrossModuleForwards(index, filePath, content) {
214
- const key = normalizePathSeparators(filePath);
210
+ const key = safelistSource.normalizePathSeparators(filePath);
215
211
  const forwards = compiler.extractCrossModuleForwards(content, filePath);
216
212
  if (forwards.length === 0) index.delete(key);
217
213
  else index.set(key, forwards);
@@ -321,7 +317,7 @@ function importedSpecifiersIn(source) {
321
317
  }
322
318
  function specifierBases(specifier, directory, aliases) {
323
319
  if (specifier.startsWith(".")) {
324
- return [normalizePathSeparators(path__namespace.resolve(directory, specifier))];
320
+ return [safelistSource.normalizePathSeparators(path__namespace.resolve(directory, specifier))];
325
321
  }
326
322
  return aliasedSpecifierBases(specifier, aliases);
327
323
  }
@@ -574,7 +570,6 @@ exports.forwardVisitKey = forwardVisitKey;
574
570
  exports.importedSpecifiersIn = importedSpecifiersIn;
575
571
  exports.isReadableProviderFile = isReadableProviderFile;
576
572
  exports.mayExportSzvFactories = mayExportSzvFactories;
577
- exports.normalizePathSeparators = normalizePathSeparators;
578
573
  exports.readTransformCache = readTransformCache;
579
574
  exports.recordCrossModuleForwards = recordCrossModuleForwards;
580
575
  exports.recordResolvedEntry = recordResolvedEntry;
@@ -5,7 +5,7 @@ const fs = require('node:fs');
5
5
  const node_crypto = require('node:crypto');
6
6
  const node_os = require('node:os');
7
7
  const lockfile = require('proper-lockfile');
8
- const htmlEscape = require('./unplugin.BkRah5Ot.cjs');
8
+ const safelistSource = require('./unplugin.KnDCM-sr.cjs');
9
9
 
10
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
11
11
 
@@ -29,9 +29,10 @@ const DEFAULT_RENAME_RETRIES = 5;
29
29
  const DEFAULT_RENAME_RETRY_DELAY_MS = 10;
30
30
  const DEFAULT_STALE_LOCK_MS = 3e4;
31
31
  const STALE_RECOVERY_DISABLED_MS = 2147483647;
32
- function resolveNextSafelistStatePaths(rootDir, cacheDir = ".csszyx/cache", outputFile = "csszyx-classes.html") {
32
+ function resolveNextSafelistStatePaths(rootDir, cacheDir = ".csszyx/cache", outputFile = safelistSource.SAFELIST_FILE) {
33
33
  const resolvedCacheDir = path__namespace.resolve(rootDir, cacheDir);
34
34
  return {
35
+ rootDir: path__namespace.resolve(rootDir),
35
36
  cacheDir: resolvedCacheDir,
36
37
  shardsDir: path__namespace.join(resolvedCacheDir, "safelist-shards"),
37
38
  snapshotPath: path__namespace.join(resolvedCacheDir, "safelist.snapshot.json"),
@@ -62,13 +63,14 @@ function materializeNextSafelist(paths, options = {}) {
62
63
  recordsBySource.set(record.data.sourcePath, record);
63
64
  }
64
65
  }
65
- const sortedSources = [...recordsBySource.values()].map(({ data }) => [data.sourcePath, htmlEscape.sortStrings(new Set(data.classes))]).sort(([left], [right]) => left.localeCompare(right));
66
- const classNames = htmlEscape.sortStrings(new Set(sortedSources.flatMap(([, classSet]) => classSet)));
66
+ const sortedSources = [...recordsBySource.values()].map(({ data }) => [data.sourcePath, safelistSource.sortStrings(new Set(data.classes))]).sort(([left], [right]) => left.localeCompare(right));
67
+ const classNames = safelistSource.sortStrings(new Set(sortedSources.flatMap(([, classSet]) => classSet)));
67
68
  const snapshot = {
68
69
  version: 1,
69
70
  sources: sortedSources.map(([sourcePath, classSet]) => [sourcePath, classSet])
70
71
  };
71
- atomicWriteFileSync(paths.outputPath, renderTailwindSourceHtml(classNames), options);
72
+ atomicWriteFileSync(paths.outputPath, safelistSource.renderSafelistFile(classNames), options);
73
+ safelistSource.removeLegacySafelists(paths.rootDir, console.warn);
72
74
  atomicWriteFileSync(paths.snapshotPath, `${JSON.stringify(snapshot, null, 2)}
73
75
  `, options);
74
76
  return {
@@ -100,14 +102,14 @@ function acquireNextSafelistStateLock(lockPath, pidOrOptions = {}) {
100
102
  } catch (error) {
101
103
  const existing2 = readLockMetadata(lockPath);
102
104
  if (existing2) {
103
- throw new Error(formatLiveLockError(existing2));
105
+ throw new NextSafelistStateLockedError(existing2);
104
106
  }
105
107
  throw error;
106
108
  }
107
109
  const existing = readLockMetadata(lockPath);
108
110
  if (existing && isLockLive(existing, options)) {
109
111
  releaseAdvisory();
110
- throw new Error(formatLiveLockError(existing));
112
+ throw new NextSafelistStateLockedError(existing);
111
113
  }
112
114
  try {
113
115
  atomicWriteFileSync(lockPath, `${JSON.stringify(metadata, null, 2)}
@@ -195,7 +197,7 @@ function sameClasses(left, right) {
195
197
  }
196
198
  function normalizeShardInput(input) {
197
199
  const sourcePath = path__namespace.resolve(input.sourcePath);
198
- const classes = htmlEscape.sortStrings(
200
+ const classes = safelistSource.sortStrings(
199
201
  new Set(input.classes.map((className) => className.trim()).filter(Boolean))
200
202
  );
201
203
  const cacheKey = input.cacheKey ?? node_crypto.createHash("sha256").update(sourcePath).update("\0").update(input.sourceHash).digest("hex");
@@ -252,15 +254,6 @@ function normalizeShardRecord(filePath, data) {
252
254
  })
253
255
  };
254
256
  }
255
- function renderTailwindSourceHtml(classNames) {
256
- if (classNames.length === 0) {
257
- return "<!-- csszyx Next safelist: empty -->\n";
258
- }
259
- const structuralHtml = classNames.map((className) => `<div class="${htmlEscape.escapeHtmlAttribute(className)}"></div>`).join("\n");
260
- const scannerCandidates = htmlEscape.renderTailwindScannerCandidates(classNames);
261
- return `${structuralHtml}
262
- ${scannerCandidates}`;
263
- }
264
257
  function createLockMetadata(options) {
265
258
  const now = new Date(options.now ?? Date.now()).toISOString();
266
259
  const pid = options.pid ?? process.pid;
@@ -357,6 +350,18 @@ function isProcessAlive(pid) {
357
350
  return false;
358
351
  }
359
352
  }
353
+ class NextSafelistStateLockedError extends Error {
354
+ holder;
355
+ /**
356
+ * @param holder - metadata read from the lock file.
357
+ */
358
+ constructor(holder) {
359
+ super(formatLiveLockError(holder));
360
+ this.name = "NextSafelistStateLockedError";
361
+ this.holder = holder;
362
+ }
363
+ }
364
+ const NEXT_WATCH_LOCK_COMMAND = "csszyx next watch";
360
365
  function formatLiveLockError(metadata) {
361
366
  return [
362
367
  `csszyx Next safelist state is already locked by process ${metadata.pid}.`,
@@ -410,7 +415,7 @@ function stableStringify(value) {
410
415
  }
411
416
  function pickEnv(env, envKeys) {
412
417
  const selected = {};
413
- for (const key of htmlEscape.sortStrings(new Set(envKeys))) {
418
+ for (const key of safelistSource.sortStrings(new Set(envKeys))) {
414
419
  const value = env[key];
415
420
  if (value !== void 0) {
416
421
  selected[key] = value;
@@ -535,6 +540,7 @@ function isNestedPackageRoot(parentRoot, candidateRoot) {
535
540
 
536
541
  function createNextStateContext(input) {
537
542
  const rootResolution = resolveNextAppRoot(input);
543
+ safelistSource.assertNoLegacySourceStylesheet(rootResolution.root);
538
544
  const cacheDir = resolveNextAppCacheDir(rootResolution.root, input.cacheDir);
539
545
  const identity = createNextCacheIdentity({
540
546
  root: rootResolution.root,
@@ -598,7 +604,7 @@ function runNextWatcherCycle(context, options = {}) {
598
604
  const lock = acquireNextSafelistStateLock(lockPath, {
599
605
  root: context.root,
600
606
  mode: context.manifestExpectation.mode,
601
- command: "csszyx next watch",
607
+ command: NEXT_WATCH_LOCK_COMMAND,
602
608
  ...options.lockOptions
603
609
  });
604
610
  try {
@@ -632,6 +638,8 @@ function runNextWatcherCycle(context, options = {}) {
632
638
  }
633
639
  }
634
640
 
641
+ exports.NEXT_WATCH_LOCK_COMMAND = NEXT_WATCH_LOCK_COMMAND;
642
+ exports.NextSafelistStateLockedError = NextSafelistStateLockedError;
635
643
  exports.createNextStateContext = createNextStateContext;
636
644
  exports.readNextGenerationManifest = readNextGenerationManifest;
637
645
  exports.runNextWatcherCycle = runNextWatcherCycle;
@@ -2,9 +2,9 @@ import { readFileSync } from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { extractCrossModuleForwards, extractCrossModuleRegistryEntries, ensureRustTransformAvailable, transformRust, transformWasm, transform } from '@csszyx/compiler';
4
4
  import { DEFAULT_IMPORTED_STATIC_SZ } from '@csszyx/types';
5
- import { c as collectSpecifierAliases, i as importedSpecifiersIn, s as specifierBases, a as resolveProviderPathWith, n as normalizePathSeparators, b as recordResolvedEntry, f as forwardVisitKey, M as MAX_FORWARD_HOPS, d as isReadableProviderFile, e as createTransformCacheKey, g as readTransformCache, w as writeTransformCache } from './unplugin.NlPvn5P4.mjs';
5
+ import { c as collectSpecifierAliases, i as importedSpecifiersIn, s as specifierBases, a as resolveProviderPathWith, b as recordResolvedEntry, f as forwardVisitKey, M as MAX_FORWARD_HOPS, d as isReadableProviderFile, e as createTransformCacheKey, g as readTransformCache, w as writeTransformCache } from './unplugin.AGR7mihU.mjs';
6
+ import { n as normalizePathSeparators, s as sortStrings } from './unplugin.CqJS0BbD.mjs';
6
7
  import { createHash } from 'node:crypto';
7
- import { s as sortStrings } from './unplugin.B3XoSRB8.mjs';
8
8
 
9
9
  function resolveImportedStaticSz(value) {
10
10
  return value ?? DEFAULT_IMPORTED_STATIC_SZ;
@@ -3,15 +3,16 @@ import * as fs from 'node:fs';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
4
  import { hostname } from 'node:os';
5
5
  import lockfile from 'proper-lockfile';
6
- import { s as sortStrings, e as escapeHtmlAttribute, r as renderTailwindScannerCandidates } from './unplugin.B3XoSRB8.mjs';
6
+ import { S as SAFELIST_FILE, s as sortStrings, r as renderSafelistFile, d as removeLegacySafelists, e as assertNoLegacySourceStylesheet } from './unplugin.CqJS0BbD.mjs';
7
7
 
8
8
  const DEFAULT_RENAME_RETRIES = 5;
9
9
  const DEFAULT_RENAME_RETRY_DELAY_MS = 10;
10
10
  const DEFAULT_STALE_LOCK_MS = 3e4;
11
11
  const STALE_RECOVERY_DISABLED_MS = 2147483647;
12
- function resolveNextSafelistStatePaths(rootDir, cacheDir = ".csszyx/cache", outputFile = "csszyx-classes.html") {
12
+ function resolveNextSafelistStatePaths(rootDir, cacheDir = ".csszyx/cache", outputFile = SAFELIST_FILE) {
13
13
  const resolvedCacheDir = path.resolve(rootDir, cacheDir);
14
14
  return {
15
+ rootDir: path.resolve(rootDir),
15
16
  cacheDir: resolvedCacheDir,
16
17
  shardsDir: path.join(resolvedCacheDir, "safelist-shards"),
17
18
  snapshotPath: path.join(resolvedCacheDir, "safelist.snapshot.json"),
@@ -48,7 +49,8 @@ function materializeNextSafelist(paths, options = {}) {
48
49
  version: 1,
49
50
  sources: sortedSources.map(([sourcePath, classSet]) => [sourcePath, classSet])
50
51
  };
51
- atomicWriteFileSync(paths.outputPath, renderTailwindSourceHtml(classNames), options);
52
+ atomicWriteFileSync(paths.outputPath, renderSafelistFile(classNames), options);
53
+ removeLegacySafelists(paths.rootDir, console.warn);
52
54
  atomicWriteFileSync(paths.snapshotPath, `${JSON.stringify(snapshot, null, 2)}
53
55
  `, options);
54
56
  return {
@@ -80,14 +82,14 @@ function acquireNextSafelistStateLock(lockPath, pidOrOptions = {}) {
80
82
  } catch (error) {
81
83
  const existing2 = readLockMetadata(lockPath);
82
84
  if (existing2) {
83
- throw new Error(formatLiveLockError(existing2));
85
+ throw new NextSafelistStateLockedError(existing2);
84
86
  }
85
87
  throw error;
86
88
  }
87
89
  const existing = readLockMetadata(lockPath);
88
90
  if (existing && isLockLive(existing, options)) {
89
91
  releaseAdvisory();
90
- throw new Error(formatLiveLockError(existing));
92
+ throw new NextSafelistStateLockedError(existing);
91
93
  }
92
94
  try {
93
95
  atomicWriteFileSync(lockPath, `${JSON.stringify(metadata, null, 2)}
@@ -232,15 +234,6 @@ function normalizeShardRecord(filePath, data) {
232
234
  })
233
235
  };
234
236
  }
235
- function renderTailwindSourceHtml(classNames) {
236
- if (classNames.length === 0) {
237
- return "<!-- csszyx Next safelist: empty -->\n";
238
- }
239
- const structuralHtml = classNames.map((className) => `<div class="${escapeHtmlAttribute(className)}"></div>`).join("\n");
240
- const scannerCandidates = renderTailwindScannerCandidates(classNames);
241
- return `${structuralHtml}
242
- ${scannerCandidates}`;
243
- }
244
237
  function createLockMetadata(options) {
245
238
  const now = new Date(options.now ?? Date.now()).toISOString();
246
239
  const pid = options.pid ?? process.pid;
@@ -337,6 +330,18 @@ function isProcessAlive(pid) {
337
330
  return false;
338
331
  }
339
332
  }
333
+ class NextSafelistStateLockedError extends Error {
334
+ holder;
335
+ /**
336
+ * @param holder - metadata read from the lock file.
337
+ */
338
+ constructor(holder) {
339
+ super(formatLiveLockError(holder));
340
+ this.name = "NextSafelistStateLockedError";
341
+ this.holder = holder;
342
+ }
343
+ }
344
+ const NEXT_WATCH_LOCK_COMMAND = "csszyx next watch";
340
345
  function formatLiveLockError(metadata) {
341
346
  return [
342
347
  `csszyx Next safelist state is already locked by process ${metadata.pid}.`,
@@ -515,6 +520,7 @@ function isNestedPackageRoot(parentRoot, candidateRoot) {
515
520
 
516
521
  function createNextStateContext(input) {
517
522
  const rootResolution = resolveNextAppRoot(input);
523
+ assertNoLegacySourceStylesheet(rootResolution.root);
518
524
  const cacheDir = resolveNextAppCacheDir(rootResolution.root, input.cacheDir);
519
525
  const identity = createNextCacheIdentity({
520
526
  root: rootResolution.root,
@@ -578,7 +584,7 @@ function runNextWatcherCycle(context, options = {}) {
578
584
  const lock = acquireNextSafelistStateLock(lockPath, {
579
585
  root: context.root,
580
586
  mode: context.manifestExpectation.mode,
581
- command: "csszyx next watch",
587
+ command: NEXT_WATCH_LOCK_COMMAND,
582
588
  ...options.lockOptions
583
589
  });
584
590
  try {
@@ -612,4 +618,4 @@ function runNextWatcherCycle(context, options = {}) {
612
618
  }
613
619
  }
614
620
 
615
- export { readNextGenerationManifest as a, createNextStateContext as c, runNextWatcherCycle as r, validateNextGenerationManifest as v, writeNextSafelistShard as w };
621
+ export { NextSafelistStateLockedError as N, NEXT_WATCH_LOCK_COMMAND as a, readNextGenerationManifest as b, createNextStateContext as c, runNextWatcherCycle as r, validateNextGenerationManifest as v, writeNextSafelistShard as w };