tailwindcss-patch 8.7.4-alpha.0 → 9.0.0-alpha.2
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/README.md +65 -5
- package/dist/{chunk-ZXW4S356.js → chunk-TOAZIPHJ.js} +295 -285
- package/dist/{chunk-6ZDYMYHE.mjs → chunk-VDWTCQ74.mjs} +259 -249
- package/dist/cli.js +4 -4
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +46 -134
- package/dist/index.d.ts +46 -134
- package/dist/index.js +5 -3
- package/dist/index.mjs +4 -2
- package/package.json +8 -3
- package/src/api/tailwindcss-patcher.ts +424 -0
- package/src/babel/index.ts +12 -0
- package/src/cache/context.ts +212 -0
- package/src/cache/store.ts +1440 -0
- package/src/cache/types.ts +71 -0
- package/src/cli.ts +20 -0
- package/src/commands/basic-handlers.ts +145 -0
- package/src/commands/cli.ts +56 -0
- package/src/commands/command-context.ts +77 -0
- package/src/commands/command-definitions.ts +102 -0
- package/src/commands/command-metadata.ts +68 -0
- package/src/commands/command-registrar.ts +39 -0
- package/src/commands/command-runtime.ts +33 -0
- package/src/commands/default-handler-map.ts +25 -0
- package/src/commands/migrate-config.ts +104 -0
- package/src/commands/migrate-handler.ts +67 -0
- package/src/commands/migration-aggregation.ts +100 -0
- package/src/commands/migration-args.ts +85 -0
- package/src/commands/migration-file-executor.ts +189 -0
- package/src/commands/migration-output.ts +115 -0
- package/src/commands/migration-report-loader.ts +26 -0
- package/src/commands/migration-report.ts +21 -0
- package/src/commands/migration-source.ts +318 -0
- package/src/commands/migration-target-files.ts +161 -0
- package/src/commands/migration-target-resolver.ts +34 -0
- package/src/commands/migration-types.ts +65 -0
- package/src/commands/restore-handler.ts +24 -0
- package/src/commands/status-handler.ts +17 -0
- package/src/commands/status-output.ts +60 -0
- package/src/commands/token-output.ts +30 -0
- package/src/commands/types.ts +137 -0
- package/src/commands/validate-handler.ts +42 -0
- package/src/commands/validate.ts +83 -0
- package/src/config/index.ts +25 -0
- package/src/config/workspace.ts +87 -0
- package/src/constants.ts +4 -0
- package/src/extraction/candidate-extractor.ts +354 -0
- package/src/index.ts +57 -0
- package/src/install/class-collector.ts +1 -0
- package/src/install/context-registry.ts +1 -0
- package/src/install/index.ts +5 -0
- package/src/install/patch-runner.ts +1 -0
- package/src/install/process-tailwindcss.ts +1 -0
- package/src/install/status.ts +1 -0
- package/src/logger.ts +5 -0
- package/src/options/legacy.ts +93 -0
- package/src/options/normalize.ts +262 -0
- package/src/options/types.ts +217 -0
- package/src/patching/operations/export-context/index.ts +110 -0
- package/src/patching/operations/export-context/postcss-v2.ts +235 -0
- package/src/patching/operations/export-context/postcss-v3.ts +249 -0
- package/src/patching/operations/extend-length-units.ts +197 -0
- package/src/patching/patch-runner.ts +46 -0
- package/src/patching/status.ts +262 -0
- package/src/runtime/class-collector.ts +105 -0
- package/src/runtime/collector.ts +148 -0
- package/src/runtime/context-registry.ts +65 -0
- package/src/runtime/process-tailwindcss.ts +115 -0
- package/src/types.ts +159 -0
- package/src/utils.ts +52 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import type { SourceEntry } from '@tailwindcss/oxide'
|
|
2
|
+
import type { PackageInfo } from 'local-pkg'
|
|
3
|
+
import type { NormalizedTailwindCssPatchOptions } from '../config'
|
|
4
|
+
import type {
|
|
5
|
+
CacheClearOptions,
|
|
6
|
+
CacheClearResult,
|
|
7
|
+
CacheContextMetadata,
|
|
8
|
+
CacheReadMeta,
|
|
9
|
+
ExtractResult,
|
|
10
|
+
TailwindCssPatchOptions,
|
|
11
|
+
TailwindTokenByFileMap,
|
|
12
|
+
TailwindTokenFileKey,
|
|
13
|
+
TailwindTokenReport,
|
|
14
|
+
} from '../types'
|
|
15
|
+
import process from 'node:process'
|
|
16
|
+
import fs from 'fs-extra'
|
|
17
|
+
import { getPackageInfoSync } from 'local-pkg'
|
|
18
|
+
import path from 'pathe'
|
|
19
|
+
import { coerce } from 'semver'
|
|
20
|
+
import { createCacheContextDescriptor } from '../cache/context'
|
|
21
|
+
import { CacheStore } from '../cache/store'
|
|
22
|
+
import { normalizeOptions } from '../config'
|
|
23
|
+
import {
|
|
24
|
+
extractValidCandidates as extractCandidates,
|
|
25
|
+
extractProjectCandidatesWithPositions,
|
|
26
|
+
groupTokensByFile,
|
|
27
|
+
} from '../extraction/candidate-extractor'
|
|
28
|
+
import { collectClassesFromContexts } from '../install/class-collector'
|
|
29
|
+
import logger from '../logger'
|
|
30
|
+
import { RuntimeCollector, TailwindV4Collector, type PatchResult, type TailwindCollector, type TailwindMajorVersion } from '../runtime/collector'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
interface PatchMemo {
|
|
34
|
+
result: PatchResult
|
|
35
|
+
snapshot: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveInstalledMajorVersion(version?: string | null) {
|
|
39
|
+
if (!version) {
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const coerced = coerce(version)
|
|
44
|
+
if (!coerced) {
|
|
45
|
+
return undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const major = coerced.major as TailwindMajorVersion
|
|
49
|
+
if (major === 2 || major === 3 || major === 4) {
|
|
50
|
+
return major
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (major >= 4) {
|
|
54
|
+
return 4
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return undefined
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateInstalledVersion(packageVersion: string | undefined, expectedMajor: TailwindMajorVersion, packageName: string) {
|
|
61
|
+
const installedMajor = resolveInstalledMajorVersion(packageVersion)
|
|
62
|
+
if (installedMajor === undefined) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (installedMajor !== expectedMajor) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Configured tailwindcss.version=${expectedMajor}, but resolved package "${packageName}" is version ${packageVersion}. Update the configuration or resolve the correct package.`,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveMajorVersionOrThrow(
|
|
74
|
+
configuredMajor: TailwindMajorVersion | undefined,
|
|
75
|
+
packageVersion: string | undefined,
|
|
76
|
+
packageName: string,
|
|
77
|
+
): TailwindMajorVersion {
|
|
78
|
+
if (configuredMajor !== undefined) {
|
|
79
|
+
validateInstalledVersion(packageVersion, configuredMajor, packageName)
|
|
80
|
+
return configuredMajor
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const installedMajor = resolveInstalledMajorVersion(packageVersion)
|
|
84
|
+
if (installedMajor !== undefined) {
|
|
85
|
+
return installedMajor
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Unable to infer Tailwind CSS major version from resolved package "${packageName}" (${packageVersion ?? 'unknown'}). Set "tailwindcss.version" to 2, 3, or 4 explicitly.`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createCollector(
|
|
94
|
+
packageInfo: PackageInfo,
|
|
95
|
+
options: NormalizedTailwindCssPatchOptions,
|
|
96
|
+
majorVersion: TailwindMajorVersion,
|
|
97
|
+
snapshotFactory: () => string,
|
|
98
|
+
): TailwindCollector {
|
|
99
|
+
if (majorVersion === 4) {
|
|
100
|
+
return new TailwindV4Collector(packageInfo, options, snapshotFactory)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return new RuntimeCollector(packageInfo, options, majorVersion, snapshotFactory)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export class TailwindcssPatcher {
|
|
107
|
+
public readonly options: NormalizedTailwindCssPatchOptions
|
|
108
|
+
public readonly packageInfo: PackageInfo
|
|
109
|
+
public readonly majorVersion: TailwindMajorVersion
|
|
110
|
+
|
|
111
|
+
private readonly cacheContext: {
|
|
112
|
+
fingerprint: string
|
|
113
|
+
metadata: CacheContextMetadata
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private readonly cacheStore: CacheStore
|
|
117
|
+
private readonly collector: TailwindCollector
|
|
118
|
+
private patchMemo: PatchMemo | undefined
|
|
119
|
+
|
|
120
|
+
constructor(options: TailwindCssPatchOptions = {}) {
|
|
121
|
+
this.options = normalizeOptions(options)
|
|
122
|
+
const packageInfo = getPackageInfoSync(
|
|
123
|
+
this.options.tailwind.packageName,
|
|
124
|
+
this.options.tailwind.resolve,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if (!packageInfo) {
|
|
128
|
+
throw new Error(`Unable to locate Tailwind CSS package "${this.options.tailwind.packageName}".`)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.packageInfo = packageInfo as PackageInfo
|
|
132
|
+
this.majorVersion = resolveMajorVersionOrThrow(
|
|
133
|
+
this.options.tailwind.versionHint,
|
|
134
|
+
this.packageInfo.version,
|
|
135
|
+
this.options.tailwind.packageName,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
this.cacheContext = createCacheContextDescriptor(
|
|
139
|
+
this.options,
|
|
140
|
+
this.packageInfo,
|
|
141
|
+
this.majorVersion,
|
|
142
|
+
)
|
|
143
|
+
this.cacheStore = new CacheStore(this.options.cache, this.cacheContext)
|
|
144
|
+
this.collector = createCollector(
|
|
145
|
+
this.packageInfo,
|
|
146
|
+
this.options,
|
|
147
|
+
this.majorVersion,
|
|
148
|
+
() => this.createPatchSnapshot(),
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async patch() {
|
|
153
|
+
const snapshot = this.collector.getPatchSnapshot()
|
|
154
|
+
if (this.patchMemo && this.patchMemo.snapshot === snapshot) {
|
|
155
|
+
return this.patchMemo.result
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const result = await this.collector.patch()
|
|
159
|
+
this.patchMemo = {
|
|
160
|
+
result,
|
|
161
|
+
snapshot: this.collector.getPatchSnapshot(),
|
|
162
|
+
}
|
|
163
|
+
return result
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async getPatchStatus() {
|
|
167
|
+
return this.collector.getPatchStatus()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
getContexts() {
|
|
171
|
+
return this.collector.getContexts()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private createPatchSnapshot() {
|
|
175
|
+
const entries: string[] = []
|
|
176
|
+
const pushSnapshot = (filePath: string) => {
|
|
177
|
+
if (!fs.pathExistsSync(filePath)) {
|
|
178
|
+
entries.push(`${filePath}:missing`)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const stat = fs.statSync(filePath)
|
|
183
|
+
entries.push(`${filePath}:${stat.size}:${Math.trunc(stat.mtimeMs)}`)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (this.options.features.exposeContext.enabled && (this.majorVersion === 2 || this.majorVersion === 3)) {
|
|
187
|
+
if (this.majorVersion === 2) {
|
|
188
|
+
pushSnapshot(path.resolve(this.packageInfo.rootPath, 'lib/jit/processTailwindFeatures.js'))
|
|
189
|
+
pushSnapshot(path.resolve(this.packageInfo.rootPath, 'lib/jit/index.js'))
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
pushSnapshot(path.resolve(this.packageInfo.rootPath, 'lib/processTailwindFeatures.js'))
|
|
193
|
+
const pluginPath = ['lib/plugin.js', 'lib/index.js']
|
|
194
|
+
.map(file => path.resolve(this.packageInfo.rootPath, file))
|
|
195
|
+
.find(file => fs.pathExistsSync(file))
|
|
196
|
+
if (pluginPath) {
|
|
197
|
+
pushSnapshot(pluginPath)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (this.options.features.extendLengthUnits?.enabled) {
|
|
203
|
+
if (this.majorVersion === 3) {
|
|
204
|
+
const target = this.options.features.extendLengthUnits.lengthUnitsFilePath ?? 'lib/util/dataTypes.js'
|
|
205
|
+
pushSnapshot(path.resolve(this.packageInfo.rootPath, target))
|
|
206
|
+
}
|
|
207
|
+
else if (this.majorVersion === 4) {
|
|
208
|
+
const distDir = path.resolve(this.packageInfo.rootPath, 'dist')
|
|
209
|
+
if (fs.pathExistsSync(distDir)) {
|
|
210
|
+
const chunkNames = fs.readdirSync(distDir)
|
|
211
|
+
.filter(entry => entry.endsWith('.js') || entry.endsWith('.mjs'))
|
|
212
|
+
.sort()
|
|
213
|
+
for (const chunkName of chunkNames) {
|
|
214
|
+
pushSnapshot(path.join(distDir, chunkName))
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
entries.push(`${distDir}:missing`)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return entries.join('|')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async collectClassSet(): Promise<Set<string>> {
|
|
227
|
+
if (this.majorVersion === 4) {
|
|
228
|
+
return this.collector.collectClassSet()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const contexts = this.getContexts()
|
|
232
|
+
return collectClassesFromContexts(contexts, this.options.filter)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async runTailwindBuildIfNeeded() {
|
|
236
|
+
await this.collector.runTailwindBuildIfNeeded?.()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private debugCacheRead(meta: CacheReadMeta) {
|
|
240
|
+
if (meta.hit) {
|
|
241
|
+
logger.debug(
|
|
242
|
+
`[cache] hit fingerprint=${meta.fingerprint?.slice(0, 12) ?? 'n/a'} schema=${meta.schemaVersion ?? 'legacy'} ${meta.details.join('; ')}`,
|
|
243
|
+
)
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
logger.debug(
|
|
248
|
+
`[cache] miss reason=${meta.reason} fingerprint=${meta.fingerprint?.slice(0, 12) ?? 'n/a'} schema=${meta.schemaVersion ?? 'legacy'} ${meta.details.join('; ')}`,
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private async mergeWithCache(set: Set<string>) {
|
|
253
|
+
if (!this.options.cache.enabled) {
|
|
254
|
+
return set
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const { data: existing, meta } = await this.cacheStore.readWithMeta()
|
|
258
|
+
this.debugCacheRead(meta)
|
|
259
|
+
if (this.options.cache.strategy === 'merge') {
|
|
260
|
+
for (const value of existing) {
|
|
261
|
+
set.add(value)
|
|
262
|
+
}
|
|
263
|
+
const writeTarget = this.areSetsEqual(existing, set)
|
|
264
|
+
? undefined
|
|
265
|
+
: await this.cacheStore.write(set)
|
|
266
|
+
if (writeTarget) {
|
|
267
|
+
logger.debug(`[cache] stored ${set.size} classes -> ${writeTarget}`)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
if (set.size > 0) {
|
|
272
|
+
const writeTarget = this.areSetsEqual(existing, set)
|
|
273
|
+
? undefined
|
|
274
|
+
: await this.cacheStore.write(set)
|
|
275
|
+
if (writeTarget) {
|
|
276
|
+
logger.debug(`[cache] stored ${set.size} classes -> ${writeTarget}`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
return existing
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return set
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private mergeWithCacheSync(set: Set<string>) {
|
|
288
|
+
if (!this.options.cache.enabled) {
|
|
289
|
+
return set
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const { data: existing, meta } = this.cacheStore.readWithMetaSync()
|
|
293
|
+
this.debugCacheRead(meta)
|
|
294
|
+
if (this.options.cache.strategy === 'merge') {
|
|
295
|
+
for (const value of existing) {
|
|
296
|
+
set.add(value)
|
|
297
|
+
}
|
|
298
|
+
const writeTarget = this.areSetsEqual(existing, set)
|
|
299
|
+
? undefined
|
|
300
|
+
: this.cacheStore.writeSync(set)
|
|
301
|
+
if (writeTarget) {
|
|
302
|
+
logger.debug(`[cache] stored ${set.size} classes -> ${writeTarget}`)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
if (set.size > 0) {
|
|
307
|
+
const writeTarget = this.areSetsEqual(existing, set)
|
|
308
|
+
? undefined
|
|
309
|
+
: this.cacheStore.writeSync(set)
|
|
310
|
+
if (writeTarget) {
|
|
311
|
+
logger.debug(`[cache] stored ${set.size} classes -> ${writeTarget}`)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
return existing
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return set
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private areSetsEqual(a: Set<string>, b: Set<string>) {
|
|
323
|
+
if (a.size !== b.size) {
|
|
324
|
+
return false
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
for (const value of a) {
|
|
328
|
+
if (!b.has(value)) {
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return true
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async getClassSet() {
|
|
337
|
+
await this.runTailwindBuildIfNeeded()
|
|
338
|
+
const set = await this.collectClassSet()
|
|
339
|
+
return this.mergeWithCache(set)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
getClassSetSync(): Set<string> | undefined {
|
|
343
|
+
if (this.majorVersion === 4) {
|
|
344
|
+
throw new Error('getClassSetSync is not supported for Tailwind CSS v4 projects. Use getClassSet instead.')
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const contexts = this.getContexts()
|
|
348
|
+
const set = collectClassesFromContexts(contexts, this.options.filter)
|
|
349
|
+
const merged = this.mergeWithCacheSync(set)
|
|
350
|
+
if (contexts.length === 0 && merged.size === 0) {
|
|
351
|
+
return undefined
|
|
352
|
+
}
|
|
353
|
+
return merged
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async extract(options?: { write?: boolean }): Promise<ExtractResult> {
|
|
357
|
+
const shouldWrite = options?.write ?? this.options.output.enabled
|
|
358
|
+
const classSet = await this.getClassSet()
|
|
359
|
+
const classList = Array.from(classSet)
|
|
360
|
+
|
|
361
|
+
const result: ExtractResult = {
|
|
362
|
+
classList,
|
|
363
|
+
classSet,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!shouldWrite || !this.options.output.file) {
|
|
367
|
+
return result
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const target = path.resolve(this.options.output.file)
|
|
371
|
+
await fs.ensureDir(path.dirname(target))
|
|
372
|
+
|
|
373
|
+
if (this.options.output.format === 'json') {
|
|
374
|
+
const spaces = typeof this.options.output.pretty === 'number' ? this.options.output.pretty : undefined
|
|
375
|
+
await fs.writeJSON(target, classList, { spaces })
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
await fs.writeFile(target, `${classList.join('\n')}\n`, 'utf8')
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
logger.success(`Tailwind CSS class list saved to ${target.replace(process.cwd(), '.')}`)
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
...result,
|
|
385
|
+
filename: target,
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async clearCache(options?: CacheClearOptions): Promise<CacheClearResult> {
|
|
390
|
+
const result = await this.cacheStore.clear(options)
|
|
391
|
+
logger.debug(
|
|
392
|
+
`[cache] clear scope=${result.scope} contexts=${result.contextsRemoved} entries=${result.entriesRemoved} files=${result.filesRemoved}`,
|
|
393
|
+
)
|
|
394
|
+
return result
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Backwards compatibility helper used by tests and API consumers.
|
|
398
|
+
extractValidCandidates = extractCandidates
|
|
399
|
+
|
|
400
|
+
async collectContentTokens(options?: { cwd?: string, sources?: SourceEntry[] }): Promise<TailwindTokenReport> {
|
|
401
|
+
return extractProjectCandidatesWithPositions({
|
|
402
|
+
cwd: options?.cwd ?? this.options.projectRoot,
|
|
403
|
+
sources: options?.sources ?? this.options.tailwind.v4?.sources ?? [],
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async collectContentTokensByFile(options?: {
|
|
408
|
+
cwd?: string
|
|
409
|
+
sources?: SourceEntry[]
|
|
410
|
+
key?: TailwindTokenFileKey
|
|
411
|
+
stripAbsolutePaths?: boolean
|
|
412
|
+
}): Promise<TailwindTokenByFileMap> {
|
|
413
|
+
const collectContentOptions = {
|
|
414
|
+
...(options?.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
415
|
+
...(options?.sources === undefined ? {} : { sources: options.sources }),
|
|
416
|
+
}
|
|
417
|
+
const report = await this.collectContentTokens(collectContentOptions)
|
|
418
|
+
const groupOptions = {
|
|
419
|
+
...(options?.key === undefined ? {} : { key: options.key }),
|
|
420
|
+
...(options?.stripAbsolutePaths === undefined ? {} : { stripAbsolutePaths: options.stripAbsolutePaths }),
|
|
421
|
+
}
|
|
422
|
+
return groupTokensByFile(report, groupOptions)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import _babelGenerate from '@babel/generator'
|
|
2
|
+
import _babelTraverse from '@babel/traverse'
|
|
3
|
+
|
|
4
|
+
export { parse, parseExpression } from '@babel/parser'
|
|
5
|
+
|
|
6
|
+
export function _interopDefaultCompat(e: any) {
|
|
7
|
+
return e && typeof e === 'object' && 'default' in e ? e.default : e
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const generate = _interopDefaultCompat(_babelGenerate) as typeof _babelGenerate
|
|
11
|
+
|
|
12
|
+
export const traverse = _interopDefaultCompat(_babelTraverse) as typeof _babelTraverse
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { PackageInfo } from 'local-pkg'
|
|
2
|
+
import type { NormalizedTailwindCssPatchOptions } from '../options/types'
|
|
3
|
+
import type { CacheContextDescriptor, CacheContextMetadata } from './types'
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
import fs from 'fs-extra'
|
|
7
|
+
import path from 'pathe'
|
|
8
|
+
import { pkgVersion } from '../constants'
|
|
9
|
+
import { CACHE_FINGERPRINT_VERSION } from './types'
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TAILWIND_CONFIG_FILES = [
|
|
12
|
+
'tailwind.config.js',
|
|
13
|
+
'tailwind.config.cjs',
|
|
14
|
+
'tailwind.config.mjs',
|
|
15
|
+
'tailwind.config.ts',
|
|
16
|
+
'tailwind.config.cts',
|
|
17
|
+
'tailwind.config.mts',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
function normalizePathname(value: string) {
|
|
21
|
+
return path.normalize(value).replaceAll('\\', '/')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveRealpathSyncSafe(value: string): string {
|
|
25
|
+
const resolved = path.resolve(value)
|
|
26
|
+
try {
|
|
27
|
+
return normalizePathname(fs.realpathSync(resolved))
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return normalizePathname(resolved)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveFileMtimeMsSync(value: string | undefined): number | undefined {
|
|
35
|
+
if (!value) {
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const stat = fs.statSync(value)
|
|
41
|
+
if (!stat.isFile()) {
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
return stat.mtimeMs
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveTailwindConfigPath(
|
|
52
|
+
options: NormalizedTailwindCssPatchOptions,
|
|
53
|
+
majorVersion: 2 | 3 | 4,
|
|
54
|
+
): string | undefined {
|
|
55
|
+
const tailwind = options.tailwind
|
|
56
|
+
const baseDir = tailwind.cwd ?? options.projectRoot
|
|
57
|
+
|
|
58
|
+
const configured = (() => {
|
|
59
|
+
if (majorVersion === 2 && tailwind.v2?.config) {
|
|
60
|
+
return tailwind.v2.config
|
|
61
|
+
}
|
|
62
|
+
if (majorVersion === 3 && tailwind.v3?.config) {
|
|
63
|
+
return tailwind.v3.config
|
|
64
|
+
}
|
|
65
|
+
return tailwind.config
|
|
66
|
+
})()
|
|
67
|
+
|
|
68
|
+
if (configured) {
|
|
69
|
+
const absolute = path.isAbsolute(configured) ? configured : path.resolve(baseDir, configured)
|
|
70
|
+
if (fs.pathExistsSync(absolute)) {
|
|
71
|
+
return resolveRealpathSyncSafe(absolute)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const candidate of DEFAULT_TAILWIND_CONFIG_FILES) {
|
|
76
|
+
const absolute = path.resolve(baseDir, candidate)
|
|
77
|
+
if (fs.pathExistsSync(absolute)) {
|
|
78
|
+
return resolveRealpathSyncSafe(absolute)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function stableSerialize(input: unknown): string {
|
|
86
|
+
if (input === null) {
|
|
87
|
+
return 'null'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof input === 'string') {
|
|
91
|
+
return JSON.stringify(input)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof input === 'number' || typeof input === 'boolean') {
|
|
95
|
+
return JSON.stringify(input)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (Array.isArray(input)) {
|
|
99
|
+
return `[${input.map(item => stableSerialize(item)).join(',')}]`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (typeof input === 'object') {
|
|
103
|
+
const entries = Object.entries(input as Record<string, unknown>)
|
|
104
|
+
.filter(([, value]) => value !== undefined)
|
|
105
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
106
|
+
.map(([key, value]) => `${JSON.stringify(key)}:${stableSerialize(value)}`)
|
|
107
|
+
return `{${entries.join(',')}}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return JSON.stringify(String(input))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function hash(input: string): string {
|
|
114
|
+
return createHash('sha256').update(input).digest('hex')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function toFingerprintOptions(normalized: NormalizedTailwindCssPatchOptions) {
|
|
118
|
+
return {
|
|
119
|
+
overwrite: normalized.overwrite,
|
|
120
|
+
output: {
|
|
121
|
+
removeUniversalSelector: normalized.output.removeUniversalSelector,
|
|
122
|
+
format: normalized.output.format,
|
|
123
|
+
},
|
|
124
|
+
features: normalized.features,
|
|
125
|
+
tailwind: {
|
|
126
|
+
packageName: normalized.tailwind.packageName,
|
|
127
|
+
cwd: normalized.tailwind.cwd,
|
|
128
|
+
config: normalized.tailwind.config,
|
|
129
|
+
postcssPlugin: normalized.tailwind.postcssPlugin,
|
|
130
|
+
versionHint: normalized.tailwind.versionHint,
|
|
131
|
+
v2: normalized.tailwind.v2,
|
|
132
|
+
v3: normalized.tailwind.v3,
|
|
133
|
+
v4: normalized.tailwind.v4,
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function createCacheContextDescriptor(
|
|
139
|
+
options: NormalizedTailwindCssPatchOptions,
|
|
140
|
+
packageInfo: PackageInfo,
|
|
141
|
+
majorVersion: 2 | 3 | 4,
|
|
142
|
+
): CacheContextDescriptor {
|
|
143
|
+
const projectRootRealpath = resolveRealpathSyncSafe(options.projectRoot)
|
|
144
|
+
const processCwdRealpath = resolveRealpathSyncSafe(process.cwd())
|
|
145
|
+
const cacheCwdRealpath = resolveRealpathSyncSafe(options.cache.cwd)
|
|
146
|
+
const tailwindPackageRootRealpath = resolveRealpathSyncSafe(packageInfo.rootPath)
|
|
147
|
+
const tailwindConfigPath = resolveTailwindConfigPath(options, majorVersion)
|
|
148
|
+
const tailwindConfigMtimeMs = resolveFileMtimeMsSync(tailwindConfigPath)
|
|
149
|
+
|
|
150
|
+
const optionsHash = hash(stableSerialize(toFingerprintOptions(options)))
|
|
151
|
+
|
|
152
|
+
const metadata: CacheContextMetadata = {
|
|
153
|
+
fingerprintVersion: CACHE_FINGERPRINT_VERSION,
|
|
154
|
+
projectRootRealpath,
|
|
155
|
+
processCwdRealpath,
|
|
156
|
+
cacheCwdRealpath,
|
|
157
|
+
...(tailwindConfigPath === undefined ? {} : { tailwindConfigPath }),
|
|
158
|
+
...(tailwindConfigMtimeMs === undefined ? {} : { tailwindConfigMtimeMs }),
|
|
159
|
+
tailwindPackageRootRealpath,
|
|
160
|
+
tailwindPackageVersion: packageInfo.version ?? 'unknown',
|
|
161
|
+
patcherVersion: pkgVersion,
|
|
162
|
+
majorVersion,
|
|
163
|
+
optionsHash,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const fingerprint = hash(stableSerialize(metadata))
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
fingerprint,
|
|
170
|
+
metadata,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function explainContextMismatch(
|
|
175
|
+
current: CacheContextMetadata,
|
|
176
|
+
cached: CacheContextMetadata,
|
|
177
|
+
): string[] {
|
|
178
|
+
const reasons: string[] = []
|
|
179
|
+
|
|
180
|
+
if (current.projectRootRealpath !== cached.projectRootRealpath) {
|
|
181
|
+
reasons.push(`project-root changed: ${cached.projectRootRealpath} -> ${current.projectRootRealpath}`)
|
|
182
|
+
}
|
|
183
|
+
if (current.processCwdRealpath !== cached.processCwdRealpath) {
|
|
184
|
+
reasons.push(`process-cwd changed: ${cached.processCwdRealpath} -> ${current.processCwdRealpath}`)
|
|
185
|
+
}
|
|
186
|
+
if (current.cacheCwdRealpath !== cached.cacheCwdRealpath) {
|
|
187
|
+
reasons.push(`cache-cwd changed: ${cached.cacheCwdRealpath} -> ${current.cacheCwdRealpath}`)
|
|
188
|
+
}
|
|
189
|
+
if ((current.tailwindConfigPath ?? '') !== (cached.tailwindConfigPath ?? '')) {
|
|
190
|
+
reasons.push(`tailwind-config path changed: ${cached.tailwindConfigPath ?? '<none>'} -> ${current.tailwindConfigPath ?? '<none>'}`)
|
|
191
|
+
}
|
|
192
|
+
if ((current.tailwindConfigMtimeMs ?? -1) !== (cached.tailwindConfigMtimeMs ?? -1)) {
|
|
193
|
+
reasons.push('tailwind-config mtime changed')
|
|
194
|
+
}
|
|
195
|
+
if (current.tailwindPackageRootRealpath !== cached.tailwindPackageRootRealpath) {
|
|
196
|
+
reasons.push(`tailwind-package root changed: ${cached.tailwindPackageRootRealpath} -> ${current.tailwindPackageRootRealpath}`)
|
|
197
|
+
}
|
|
198
|
+
if (current.tailwindPackageVersion !== cached.tailwindPackageVersion) {
|
|
199
|
+
reasons.push(`tailwind-package version changed: ${cached.tailwindPackageVersion} -> ${current.tailwindPackageVersion}`)
|
|
200
|
+
}
|
|
201
|
+
if (current.patcherVersion !== cached.patcherVersion) {
|
|
202
|
+
reasons.push(`patcher version changed: ${cached.patcherVersion} -> ${current.patcherVersion}`)
|
|
203
|
+
}
|
|
204
|
+
if (current.majorVersion !== cached.majorVersion) {
|
|
205
|
+
reasons.push(`major version changed: ${cached.majorVersion} -> ${current.majorVersion}`)
|
|
206
|
+
}
|
|
207
|
+
if (current.optionsHash !== cached.optionsHash) {
|
|
208
|
+
reasons.push(`patch options hash changed: ${cached.optionsHash.slice(0, 12)} -> ${current.optionsHash.slice(0, 12)}`)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return reasons
|
|
212
|
+
}
|