tailwindcss-patch 8.0.0 → 8.2.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.
@@ -12,6 +12,9 @@ var CacheStore = class {
12
12
  async ensureDir() {
13
13
  await fs.ensureDir(this.options.dir);
14
14
  }
15
+ ensureDirSync() {
16
+ fs.ensureDirSync(this.options.dir);
17
+ }
15
18
  async write(data) {
16
19
  if (!this.options.enabled) {
17
20
  return void 0;
@@ -25,6 +28,19 @@ var CacheStore = class {
25
28
  return void 0;
26
29
  }
27
30
  }
31
+ writeSync(data) {
32
+ if (!this.options.enabled) {
33
+ return void 0;
34
+ }
35
+ try {
36
+ this.ensureDirSync();
37
+ fs.writeJSONSync(this.options.path, Array.from(data));
38
+ return this.options.path;
39
+ } catch (error) {
40
+ logger_default.error("Unable to persist Tailwind class cache", error);
41
+ return void 0;
42
+ }
43
+ }
28
44
  async read() {
29
45
  if (!this.options.enabled) {
30
46
  return /* @__PURE__ */ new Set();
@@ -48,10 +64,35 @@ var CacheStore = class {
48
64
  }
49
65
  return /* @__PURE__ */ new Set();
50
66
  }
67
+ readSync() {
68
+ if (!this.options.enabled) {
69
+ return /* @__PURE__ */ new Set();
70
+ }
71
+ try {
72
+ const exists = fs.pathExistsSync(this.options.path);
73
+ if (!exists) {
74
+ return /* @__PURE__ */ new Set();
75
+ }
76
+ const data = fs.readJSONSync(this.options.path);
77
+ if (Array.isArray(data)) {
78
+ return new Set(data.filter((item) => typeof item === "string"));
79
+ }
80
+ } catch (error) {
81
+ logger_default.warn("Unable to read Tailwind class cache, removing invalid file.", error);
82
+ try {
83
+ fs.removeSync(this.options.path);
84
+ } catch (cleanupError) {
85
+ logger_default.error("Failed to clean up invalid cache file", cleanupError);
86
+ }
87
+ }
88
+ return /* @__PURE__ */ new Set();
89
+ }
51
90
  };
52
91
 
53
92
  // src/extraction/candidate-extractor.ts
93
+ import { promises as fs2 } from "fs";
54
94
  import process from "process";
95
+ import path from "pathe";
55
96
  async function importNode() {
56
97
  return import("@tailwindcss/node");
57
98
  }
@@ -99,6 +140,139 @@ async function extractValidCandidates(options) {
99
140
  );
100
141
  return validCandidates;
101
142
  }
143
+ function normalizeSources(sources, cwd) {
144
+ const baseSources = sources?.length ? sources : [
145
+ {
146
+ base: cwd,
147
+ pattern: "**/*",
148
+ negated: false
149
+ }
150
+ ];
151
+ return baseSources.map((source) => ({
152
+ base: source.base ?? cwd,
153
+ pattern: source.pattern,
154
+ negated: source.negated
155
+ }));
156
+ }
157
+ function buildLineOffsets(content) {
158
+ const offsets = [0];
159
+ for (let i = 0; i < content.length; i++) {
160
+ if (content[i] === "\n") {
161
+ offsets.push(i + 1);
162
+ }
163
+ }
164
+ if (offsets[offsets.length - 1] !== content.length) {
165
+ offsets.push(content.length);
166
+ }
167
+ return offsets;
168
+ }
169
+ function resolveLineMeta(content, offsets, index) {
170
+ let low = 0;
171
+ let high = offsets.length - 1;
172
+ while (low <= high) {
173
+ const mid = Math.floor((low + high) / 2);
174
+ const start = offsets[mid];
175
+ const nextStart = offsets[mid + 1] ?? content.length;
176
+ if (index < start) {
177
+ high = mid - 1;
178
+ continue;
179
+ }
180
+ if (index >= nextStart) {
181
+ low = mid + 1;
182
+ continue;
183
+ }
184
+ const line = mid + 1;
185
+ const column = index - start + 1;
186
+ const lineEnd = content.indexOf("\n", start);
187
+ const lineText = content.slice(start, lineEnd === -1 ? content.length : lineEnd);
188
+ return { line, column, lineText };
189
+ }
190
+ const lastStart = offsets[offsets.length - 2] ?? 0;
191
+ return {
192
+ line: offsets.length - 1,
193
+ column: index - lastStart + 1,
194
+ lineText: content.slice(lastStart)
195
+ };
196
+ }
197
+ function toExtension(filename) {
198
+ const ext = path.extname(filename).replace(/^\./, "");
199
+ return ext || "txt";
200
+ }
201
+ function toRelativeFile(cwd, filename) {
202
+ const relative = path.relative(cwd, filename);
203
+ return relative === "" ? path.basename(filename) : relative;
204
+ }
205
+ async function extractProjectCandidatesWithPositions(options) {
206
+ const cwd = options?.cwd ? path.resolve(options.cwd) : process.cwd();
207
+ const normalizedSources = normalizeSources(options?.sources, cwd);
208
+ const { Scanner } = await importOxide();
209
+ const scanner = new Scanner({
210
+ sources: normalizedSources
211
+ });
212
+ const files = scanner.files ?? [];
213
+ const entries = [];
214
+ const skipped = [];
215
+ for (const file of files) {
216
+ let content;
217
+ try {
218
+ content = await fs2.readFile(file, "utf8");
219
+ } catch (error) {
220
+ skipped.push({
221
+ file,
222
+ reason: error instanceof Error ? error.message : "Unknown error"
223
+ });
224
+ continue;
225
+ }
226
+ const extension = toExtension(file);
227
+ const matches = scanner.getCandidatesWithPositions({
228
+ file,
229
+ content,
230
+ extension
231
+ });
232
+ if (!matches.length) {
233
+ continue;
234
+ }
235
+ const offsets = buildLineOffsets(content);
236
+ const relativeFile = toRelativeFile(cwd, file);
237
+ for (const match of matches) {
238
+ const info = resolveLineMeta(content, offsets, match.position);
239
+ entries.push({
240
+ rawCandidate: match.candidate,
241
+ file,
242
+ relativeFile,
243
+ extension,
244
+ start: match.position,
245
+ end: match.position + match.candidate.length,
246
+ length: match.candidate.length,
247
+ line: info.line,
248
+ column: info.column,
249
+ lineText: info.lineText
250
+ });
251
+ }
252
+ }
253
+ return {
254
+ entries,
255
+ filesScanned: files.length,
256
+ skippedFiles: skipped,
257
+ sources: normalizedSources
258
+ };
259
+ }
260
+ function groupTokensByFile(report, options) {
261
+ const key = options?.key ?? "relative";
262
+ const stripAbsolute = options?.stripAbsolutePaths ?? key !== "absolute";
263
+ return report.entries.reduce((acc, entry) => {
264
+ const bucketKey = key === "absolute" ? entry.file : entry.relativeFile;
265
+ if (!acc[bucketKey]) {
266
+ acc[bucketKey] = [];
267
+ }
268
+ const value = stripAbsolute ? {
269
+ ...entry,
270
+ file: entry.relativeFile
271
+ } : entry;
272
+ acc[bucketKey].push(value);
273
+ return acc;
274
+ }, {});
275
+ }
102
276
 
103
277
  // src/options/legacy.ts
104
278
  function normalizeLegacyFeatures(patch) {
@@ -201,7 +375,7 @@ function fromUnifiedConfig(registry) {
201
375
 
202
376
  // src/options/normalize.ts
203
377
  import process2 from "process";
204
- import path from "pathe";
378
+ import path2 from "pathe";
205
379
 
206
380
  // src/constants.ts
207
381
  var pkgName = "tailwindcss-patch";
@@ -219,7 +393,7 @@ function toPrettyValue(value) {
219
393
  function normalizeCacheOptions(cache, projectRoot) {
220
394
  let enabled = false;
221
395
  let cwd = projectRoot;
222
- let dir = path.resolve(cwd, "node_modules/.cache", pkgName);
396
+ let dir = path2.resolve(cwd, "node_modules/.cache", pkgName);
223
397
  let file = "class-cache.json";
224
398
  let strategy = "merge";
225
399
  if (typeof cache === "boolean") {
@@ -227,11 +401,11 @@ function normalizeCacheOptions(cache, projectRoot) {
227
401
  } else if (typeof cache === "object" && cache) {
228
402
  enabled = cache.enabled ?? true;
229
403
  cwd = cache.cwd ?? cwd;
230
- dir = cache.dir ? path.resolve(cache.dir) : path.resolve(cwd, "node_modules/.cache", pkgName);
404
+ dir = cache.dir ? path2.resolve(cache.dir) : path2.resolve(cwd, "node_modules/.cache", pkgName);
231
405
  file = cache.file ?? file;
232
406
  strategy = cache.strategy ?? strategy;
233
407
  }
234
- const filename = path.resolve(dir, file);
408
+ const filename = path2.resolve(dir, file);
235
409
  return {
236
410
  enabled,
237
411
  cwd,
@@ -294,8 +468,8 @@ function normalizeExtendLengthUnitsOptions(features) {
294
468
  };
295
469
  }
296
470
  function normalizeTailwindV4Options(v4, fallbackBase) {
297
- const base = v4?.base ? path.resolve(v4.base) : fallbackBase;
298
- const cssEntries = Array.isArray(v4?.cssEntries) ? v4.cssEntries.filter((entry) => Boolean(entry)).map((entry) => path.resolve(entry)) : [];
471
+ const base = v4?.base ? path2.resolve(v4.base) : fallbackBase;
472
+ const cssEntries = Array.isArray(v4?.cssEntries) ? v4.cssEntries.filter((entry) => Boolean(entry)).map((entry) => path2.resolve(entry)) : [];
299
473
  const sources = v4?.sources?.length ? v4.sources : [
300
474
  {
301
475
  base,
@@ -331,7 +505,7 @@ function normalizeTailwindOptions(tailwind, projectRoot) {
331
505
  };
332
506
  }
333
507
  function normalizeOptions(options = {}) {
334
- const projectRoot = options.cwd ? path.resolve(options.cwd) : process2.cwd();
508
+ const projectRoot = options.cwd ? path2.resolve(options.cwd) : process2.cwd();
335
509
  const overwrite = options.overwrite ?? true;
336
510
  const output = normalizeOutputOptions(options.output);
337
511
  const cache = normalizeCacheOptions(options.cache, projectRoot);
@@ -363,8 +537,8 @@ function normalizeOptions(options = {}) {
363
537
 
364
538
  // src/runtime/class-collector.ts
365
539
  import process3 from "process";
366
- import fs2 from "fs-extra";
367
- import path2 from "pathe";
540
+ import fs3 from "fs-extra";
541
+ import path3 from "pathe";
368
542
 
369
543
  // src/utils.ts
370
544
  function isObject(val) {
@@ -422,11 +596,11 @@ async function collectClassesFromTailwindV4(options) {
422
596
  });
423
597
  if (v4Options.cssEntries.length > 0) {
424
598
  for (const entry of v4Options.cssEntries) {
425
- const filePath = path2.isAbsolute(entry) ? entry : path2.resolve(options.projectRoot, entry);
426
- if (!await fs2.pathExists(filePath)) {
599
+ const filePath = path3.isAbsolute(entry) ? entry : path3.resolve(options.projectRoot, entry);
600
+ if (!await fs3.pathExists(filePath)) {
427
601
  continue;
428
602
  }
429
- const css = await fs2.readFile(filePath, "utf8");
603
+ const css = await fs3.readFile(filePath, "utf8");
430
604
  const candidates = await extractValidCandidates({
431
605
  cwd: options.projectRoot,
432
606
  base: v4Options.base,
@@ -457,23 +631,23 @@ async function collectClassesFromTailwindV4(options) {
457
631
 
458
632
  // src/runtime/context-registry.ts
459
633
  import { createRequire } from "module";
460
- import fs3 from "fs-extra";
461
- import path3 from "pathe";
634
+ import fs4 from "fs-extra";
635
+ import path4 from "pathe";
462
636
  var require2 = createRequire(import.meta.url);
463
637
  function resolveRuntimeEntry(packageInfo, majorVersion) {
464
638
  const root = packageInfo.rootPath;
465
639
  if (majorVersion === 2) {
466
- const jitIndex = path3.join(root, "lib/jit/index.js");
467
- if (fs3.existsSync(jitIndex)) {
640
+ const jitIndex = path4.join(root, "lib/jit/index.js");
641
+ if (fs4.existsSync(jitIndex)) {
468
642
  return jitIndex;
469
643
  }
470
644
  } else if (majorVersion === 3) {
471
- const plugin = path3.join(root, "lib/plugin.js");
472
- const index = path3.join(root, "lib/index.js");
473
- if (fs3.existsSync(plugin)) {
645
+ const plugin = path4.join(root, "lib/plugin.js");
646
+ const index = path4.join(root, "lib/index.js");
647
+ if (fs4.existsSync(plugin)) {
474
648
  return plugin;
475
649
  }
476
- if (fs3.existsSync(index)) {
650
+ if (fs4.existsSync(index)) {
477
651
  return index;
478
652
  }
479
653
  }
@@ -506,12 +680,12 @@ function loadRuntimeContexts(packageInfo, majorVersion, refProperty) {
506
680
 
507
681
  // src/runtime/process-tailwindcss.ts
508
682
  import { createRequire as createRequire2 } from "module";
509
- import path4 from "pathe";
683
+ import path5 from "pathe";
510
684
  import postcss from "postcss";
511
685
  import { loadConfig } from "tailwindcss-config";
512
686
  var require3 = createRequire2(import.meta.url);
513
687
  async function resolveConfigPath(options) {
514
- if (options.config && path4.isAbsolute(options.config)) {
688
+ if (options.config && path5.isAbsolute(options.config)) {
515
689
  return options.config;
516
690
  }
517
691
  const result = await loadConfig({ cwd: options.cwd });
@@ -543,14 +717,14 @@ async function runTailwindBuild(options) {
543
717
 
544
718
  // src/api/tailwindcss-patcher.ts
545
719
  import process4 from "process";
546
- import fs6 from "fs-extra";
720
+ import fs7 from "fs-extra";
547
721
  import { getPackageInfoSync } from "local-pkg";
548
- import path7 from "pathe";
722
+ import path8 from "pathe";
549
723
  import { coerce } from "semver";
550
724
 
551
725
  // src/patching/operations/export-context/index.ts
552
- import fs4 from "fs-extra";
553
- import path5 from "pathe";
726
+ import fs5 from "fs-extra";
727
+ import path6 from "pathe";
554
728
 
555
729
  // src/patching/operations/export-context/postcss-v2.ts
556
730
  import * as t from "@babel/types";
@@ -589,8 +763,8 @@ function transformProcessTailwindFeaturesReturnContextV2(content) {
589
763
  });
590
764
  let hasPatched = false;
591
765
  traverse(ast, {
592
- FunctionDeclaration(path8) {
593
- const node = path8.node;
766
+ FunctionDeclaration(path9) {
767
+ const node = path9.node;
594
768
  if (node.id?.name !== "processTailwindFeatures" || node.body.body.length !== 1 || !t.isReturnStatement(node.body.body[0])) {
595
769
  return;
596
770
  }
@@ -621,8 +795,8 @@ function transformPostcssPluginV2(content, options) {
621
795
  const ast = parse(content);
622
796
  let hasPatched = false;
623
797
  traverse(ast, {
624
- Program(path8) {
625
- const program = path8.node;
798
+ Program(path9) {
799
+ const program = path9.node;
626
800
  const index = program.body.findIndex((statement) => {
627
801
  return t.isFunctionDeclaration(statement) && statement.id?.name === "_default";
628
802
  });
@@ -656,11 +830,11 @@ function transformPostcssPluginV2(content, options) {
656
830
  );
657
831
  }
658
832
  },
659
- FunctionDeclaration(path8) {
833
+ FunctionDeclaration(path9) {
660
834
  if (hasPatched) {
661
835
  return;
662
836
  }
663
- const fn = path8.node;
837
+ const fn = path9.node;
664
838
  if (fn.id?.name !== "_default") {
665
839
  return;
666
840
  }
@@ -749,8 +923,8 @@ function transformProcessTailwindFeaturesReturnContext(content) {
749
923
  const ast = parse(content);
750
924
  let hasPatched = false;
751
925
  traverse(ast, {
752
- FunctionDeclaration(path8) {
753
- const node = path8.node;
926
+ FunctionDeclaration(path9) {
927
+ const node = path9.node;
754
928
  if (node.id?.name !== "processTailwindFeatures" || node.body.body.length !== 1) {
755
929
  return;
756
930
  }
@@ -782,8 +956,8 @@ function transformPostcssPlugin(content, { refProperty }) {
782
956
  const valueMember = t2.memberExpression(refIdentifier, t2.identifier("value"));
783
957
  let hasPatched = false;
784
958
  traverse(ast, {
785
- Program(path8) {
786
- const program = path8.node;
959
+ Program(path9) {
960
+ const program = path9.node;
787
961
  const index = program.body.findIndex((statement) => {
788
962
  return t2.isExpressionStatement(statement) && t2.isAssignmentExpression(statement.expression) && t2.isMemberExpression(statement.expression.left) && t2.isFunctionExpression(statement.expression.right) && statement.expression.right.id?.name === "tailwindcss";
789
963
  });
@@ -821,11 +995,11 @@ function transformPostcssPlugin(content, { refProperty }) {
821
995
  );
822
996
  }
823
997
  },
824
- FunctionExpression(path8) {
998
+ FunctionExpression(path9) {
825
999
  if (hasPatched) {
826
1000
  return;
827
1001
  }
828
- const fn = path8.node;
1002
+ const fn = path9.node;
829
1003
  if (fn.id?.name !== "tailwindcss" || fn.body.body.length !== 1) {
830
1004
  return;
831
1005
  }
@@ -899,7 +1073,7 @@ function writeFileIfRequired(filePath, code, overwrite, successMessage) {
899
1073
  if (!overwrite) {
900
1074
  return;
901
1075
  }
902
- fs4.writeFileSync(filePath, code, {
1076
+ fs5.writeFileSync(filePath, code, {
903
1077
  encoding: "utf8"
904
1078
  });
905
1079
  logger_default.success(successMessage);
@@ -912,9 +1086,9 @@ function applyExposeContextPatch(params) {
912
1086
  };
913
1087
  if (majorVersion === 3) {
914
1088
  const processFileRelative = "lib/processTailwindFeatures.js";
915
- const processFilePath = path5.resolve(rootDir, processFileRelative);
916
- if (fs4.existsSync(processFilePath)) {
917
- const content = fs4.readFileSync(processFilePath, "utf8");
1089
+ const processFilePath = path6.resolve(rootDir, processFileRelative);
1090
+ if (fs5.existsSync(processFilePath)) {
1091
+ const content = fs5.readFileSync(processFilePath, "utf8");
918
1092
  const { code, hasPatched } = transformProcessTailwindFeaturesReturnContext(content);
919
1093
  result.files[processFileRelative] = code;
920
1094
  if (!hasPatched) {
@@ -928,10 +1102,10 @@ function applyExposeContextPatch(params) {
928
1102
  }
929
1103
  }
930
1104
  const pluginCandidates = ["lib/plugin.js", "lib/index.js"];
931
- const pluginRelative = pluginCandidates.find((candidate) => fs4.existsSync(path5.resolve(rootDir, candidate)));
1105
+ const pluginRelative = pluginCandidates.find((candidate) => fs5.existsSync(path6.resolve(rootDir, candidate)));
932
1106
  if (pluginRelative) {
933
- const pluginPath = path5.resolve(rootDir, pluginRelative);
934
- const content = fs4.readFileSync(pluginPath, "utf8");
1107
+ const pluginPath = path6.resolve(rootDir, pluginRelative);
1108
+ const content = fs5.readFileSync(pluginPath, "utf8");
935
1109
  const { code, hasPatched } = transformPostcssPlugin(content, { refProperty });
936
1110
  result.files[pluginRelative] = code;
937
1111
  if (!hasPatched) {
@@ -946,9 +1120,9 @@ function applyExposeContextPatch(params) {
946
1120
  }
947
1121
  } else if (majorVersion === 2) {
948
1122
  const processFileRelative = "lib/jit/processTailwindFeatures.js";
949
- const processFilePath = path5.resolve(rootDir, processFileRelative);
950
- if (fs4.existsSync(processFilePath)) {
951
- const content = fs4.readFileSync(processFilePath, "utf8");
1123
+ const processFilePath = path6.resolve(rootDir, processFileRelative);
1124
+ if (fs5.existsSync(processFilePath)) {
1125
+ const content = fs5.readFileSync(processFilePath, "utf8");
952
1126
  const { code, hasPatched } = transformProcessTailwindFeaturesReturnContextV2(content);
953
1127
  result.files[processFileRelative] = code;
954
1128
  if (!hasPatched) {
@@ -962,9 +1136,9 @@ function applyExposeContextPatch(params) {
962
1136
  }
963
1137
  }
964
1138
  const pluginRelative = "lib/jit/index.js";
965
- const pluginPath = path5.resolve(rootDir, pluginRelative);
966
- if (fs4.existsSync(pluginPath)) {
967
- const content = fs4.readFileSync(pluginPath, "utf8");
1139
+ const pluginPath = path6.resolve(rootDir, pluginRelative);
1140
+ if (fs5.existsSync(pluginPath)) {
1141
+ const content = fs5.readFileSync(pluginPath, "utf8");
968
1142
  const { code, hasPatched } = transformPostcssPluginV2(content, { refProperty });
969
1143
  result.files[pluginRelative] = code;
970
1144
  if (!hasPatched) {
@@ -983,29 +1157,29 @@ function applyExposeContextPatch(params) {
983
1157
 
984
1158
  // src/patching/operations/extend-length-units.ts
985
1159
  import * as t3 from "@babel/types";
986
- import fs5 from "fs-extra";
987
- import path6 from "pathe";
1160
+ import fs6 from "fs-extra";
1161
+ import path7 from "pathe";
988
1162
  function updateLengthUnitsArray(content, options) {
989
1163
  const { variableName = "lengthUnits", units } = options;
990
1164
  const ast = parse(content);
991
1165
  let arrayRef;
992
1166
  let changed = false;
993
1167
  traverse(ast, {
994
- Identifier(path8) {
995
- if (path8.node.name === variableName && t3.isVariableDeclarator(path8.parent) && t3.isArrayExpression(path8.parent.init)) {
996
- arrayRef = path8.parent.init;
1168
+ Identifier(path9) {
1169
+ if (path9.node.name === variableName && t3.isVariableDeclarator(path9.parent) && t3.isArrayExpression(path9.parent.init)) {
1170
+ arrayRef = path9.parent.init;
997
1171
  const existing = new Set(
998
- path8.parent.init.elements.map((element) => t3.isStringLiteral(element) ? element.value : void 0).filter(Boolean)
1172
+ path9.parent.init.elements.map((element) => t3.isStringLiteral(element) ? element.value : void 0).filter(Boolean)
999
1173
  );
1000
1174
  for (const unit of units) {
1001
1175
  if (!existing.has(unit)) {
1002
- path8.parent.init.elements = path8.parent.init.elements.map((element) => {
1176
+ path9.parent.init.elements = path9.parent.init.elements.map((element) => {
1003
1177
  if (t3.isStringLiteral(element)) {
1004
1178
  return t3.stringLiteral(element.value);
1005
1179
  }
1006
1180
  return element;
1007
1181
  });
1008
- path8.parent.init.elements.push(t3.stringLiteral(unit));
1182
+ path9.parent.init.elements.push(t3.stringLiteral(unit));
1009
1183
  changed = true;
1010
1184
  }
1011
1185
  }
@@ -1026,12 +1200,12 @@ function applyExtendLengthUnitsPatchV3(rootDir, options) {
1026
1200
  lengthUnitsFilePath: options.lengthUnitsFilePath ?? "lib/util/dataTypes.js",
1027
1201
  variableName: options.variableName ?? "lengthUnits"
1028
1202
  };
1029
- const dataTypesFilePath = path6.resolve(rootDir, opts.lengthUnitsFilePath);
1030
- const exists = fs5.existsSync(dataTypesFilePath);
1203
+ const dataTypesFilePath = path7.resolve(rootDir, opts.lengthUnitsFilePath);
1204
+ const exists = fs6.existsSync(dataTypesFilePath);
1031
1205
  if (!exists) {
1032
1206
  return { changed: false, code: void 0 };
1033
1207
  }
1034
- const content = fs5.readFileSync(dataTypesFilePath, "utf8");
1208
+ const content = fs6.readFileSync(dataTypesFilePath, "utf8");
1035
1209
  const { arrayRef, changed } = updateLengthUnitsArray(content, opts);
1036
1210
  if (!arrayRef || !changed) {
1037
1211
  return { changed: false, code: void 0 };
@@ -1042,8 +1216,8 @@ function applyExtendLengthUnitsPatchV3(rootDir, options) {
1042
1216
  if (arrayRef.start != null && arrayRef.end != null) {
1043
1217
  const nextCode = `${content.slice(0, arrayRef.start)}${code}${content.slice(arrayRef.end)}`;
1044
1218
  if (opts.overwrite) {
1045
- const target = opts.destPath ? path6.resolve(opts.destPath) : dataTypesFilePath;
1046
- fs5.writeFileSync(target, nextCode, "utf8");
1219
+ const target = opts.destPath ? path7.resolve(opts.destPath) : dataTypesFilePath;
1220
+ fs6.writeFileSync(target, nextCode, "utf8");
1047
1221
  logger_default.success("Patched Tailwind CSS length unit list (v3).");
1048
1222
  }
1049
1223
  return {
@@ -1061,16 +1235,16 @@ function applyExtendLengthUnitsPatchV4(rootDir, options) {
1061
1235
  return { files: [], changed: false };
1062
1236
  }
1063
1237
  const opts = { ...options };
1064
- const distDir = path6.resolve(rootDir, "dist");
1065
- if (!fs5.existsSync(distDir)) {
1238
+ const distDir = path7.resolve(rootDir, "dist");
1239
+ if (!fs6.existsSync(distDir)) {
1066
1240
  return { files: [], changed: false };
1067
1241
  }
1068
- const entries = fs5.readdirSync(distDir);
1242
+ const entries = fs6.readdirSync(distDir);
1069
1243
  const chunkNames = entries.filter((entry) => entry.endsWith(".js") || entry.endsWith(".mjs"));
1070
1244
  const pattern = /\[\s*["']cm["'],\s*["']mm["'],[\w,"']+\]/;
1071
1245
  const candidates = chunkNames.map((chunkName) => {
1072
- const file = path6.join(distDir, chunkName);
1073
- const code = fs5.readFileSync(file, "utf8");
1246
+ const file = path7.join(distDir, chunkName);
1247
+ const code = fs6.readFileSync(file, "utf8");
1074
1248
  const match = pattern.exec(code);
1075
1249
  if (!match) {
1076
1250
  return null;
@@ -1086,13 +1260,13 @@ function applyExtendLengthUnitsPatchV4(rootDir, options) {
1086
1260
  const { code, file, match } = item;
1087
1261
  const ast = parse(match[0], { sourceType: "unambiguous" });
1088
1262
  traverse(ast, {
1089
- ArrayExpression(path8) {
1263
+ ArrayExpression(path9) {
1090
1264
  for (const unit of opts.units) {
1091
- if (path8.node.elements.some((element) => t3.isStringLiteral(element) && element.value === unit)) {
1265
+ if (path9.node.elements.some((element) => t3.isStringLiteral(element) && element.value === unit)) {
1092
1266
  item.hasPatched = true;
1093
1267
  return;
1094
1268
  }
1095
- path8.node.elements.push(t3.stringLiteral(unit));
1269
+ path9.node.elements.push(t3.stringLiteral(unit));
1096
1270
  }
1097
1271
  }
1098
1272
  });
@@ -1110,7 +1284,7 @@ function applyExtendLengthUnitsPatchV4(rootDir, options) {
1110
1284
  }
1111
1285
  ]);
1112
1286
  if (opts.overwrite) {
1113
- fs5.writeFileSync(file, item.code, "utf8");
1287
+ fs6.writeFileSync(file, item.code, "utf8");
1114
1288
  }
1115
1289
  }
1116
1290
  if (candidates.some((file) => !file.hasPatched)) {
@@ -1245,6 +1419,13 @@ var TailwindcssPatcher = class {
1245
1419
  const contexts = this.getContexts();
1246
1420
  return collectClassesFromContexts(contexts, this.options.filter);
1247
1421
  }
1422
+ collectClassSetSync() {
1423
+ if (this.majorVersion === 4) {
1424
+ throw new Error("getClassSetSync is not supported for Tailwind CSS v4 projects. Use getClassSet instead.");
1425
+ }
1426
+ const contexts = this.getContexts();
1427
+ return collectClassesFromContexts(contexts, this.options.filter);
1428
+ }
1248
1429
  async mergeWithCache(set) {
1249
1430
  if (!this.options.cache.enabled) {
1250
1431
  return set;
@@ -1264,11 +1445,34 @@ var TailwindcssPatcher = class {
1264
1445
  }
1265
1446
  return set;
1266
1447
  }
1448
+ mergeWithCacheSync(set) {
1449
+ if (!this.options.cache.enabled) {
1450
+ return set;
1451
+ }
1452
+ const existing = this.cacheStore.readSync();
1453
+ if (this.options.cache.strategy === "merge") {
1454
+ for (const value of existing) {
1455
+ set.add(value);
1456
+ }
1457
+ this.cacheStore.writeSync(set);
1458
+ } else {
1459
+ if (set.size > 0) {
1460
+ this.cacheStore.writeSync(set);
1461
+ } else {
1462
+ return existing;
1463
+ }
1464
+ }
1465
+ return set;
1466
+ }
1267
1467
  async getClassSet() {
1268
1468
  await this.runTailwindBuildIfNeeded();
1269
1469
  const set = await this.collectClassSet();
1270
1470
  return this.mergeWithCache(set);
1271
1471
  }
1472
+ getClassSetSync() {
1473
+ const set = this.collectClassSetSync();
1474
+ return this.mergeWithCacheSync(set);
1475
+ }
1272
1476
  async extract(options) {
1273
1477
  const shouldWrite = options?.write ?? this.options.output.enabled;
1274
1478
  const classSet = await this.getClassSet();
@@ -1280,13 +1484,13 @@ var TailwindcssPatcher = class {
1280
1484
  if (!shouldWrite || !this.options.output.file) {
1281
1485
  return result;
1282
1486
  }
1283
- const target = path7.resolve(this.options.output.file);
1284
- await fs6.ensureDir(path7.dirname(target));
1487
+ const target = path8.resolve(this.options.output.file);
1488
+ await fs7.ensureDir(path8.dirname(target));
1285
1489
  if (this.options.output.format === "json") {
1286
1490
  const spaces = typeof this.options.output.pretty === "number" ? this.options.output.pretty : void 0;
1287
- await fs6.writeJSON(target, classList, { spaces });
1491
+ await fs7.writeJSON(target, classList, { spaces });
1288
1492
  } else {
1289
- await fs6.writeFile(target, `${classList.join("\n")}
1493
+ await fs7.writeFile(target, `${classList.join("\n")}
1290
1494
  `, "utf8");
1291
1495
  }
1292
1496
  logger_default.success(`Tailwind CSS class list saved to ${target.replace(process4.cwd(), ".")}`);
@@ -1297,6 +1501,22 @@ var TailwindcssPatcher = class {
1297
1501
  }
1298
1502
  // Backwards compatibility helper used by tests and API consumers.
1299
1503
  extractValidCandidates = extractValidCandidates;
1504
+ async collectContentTokens(options) {
1505
+ return extractProjectCandidatesWithPositions({
1506
+ cwd: options?.cwd ?? this.options.projectRoot,
1507
+ sources: options?.sources ?? this.options.tailwind.v4?.sources ?? []
1508
+ });
1509
+ }
1510
+ async collectContentTokensByFile(options) {
1511
+ const report = await this.collectContentTokens({
1512
+ cwd: options?.cwd,
1513
+ sources: options?.sources
1514
+ });
1515
+ return groupTokensByFile(report, {
1516
+ key: options?.key,
1517
+ stripAbsolutePaths: options?.stripAbsolutePaths
1518
+ });
1519
+ }
1300
1520
  };
1301
1521
 
1302
1522
  export {
@@ -1305,6 +1525,8 @@ export {
1305
1525
  extractRawCandidatesWithPositions,
1306
1526
  extractRawCandidates,
1307
1527
  extractValidCandidates,
1528
+ extractProjectCandidatesWithPositions,
1529
+ groupTokensByFile,
1308
1530
  fromLegacyOptions,
1309
1531
  fromUnifiedConfig,
1310
1532
  normalizeOptions,