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.
Files changed (70) hide show
  1. package/README.md +65 -5
  2. package/dist/{chunk-ZXW4S356.js → chunk-TOAZIPHJ.js} +295 -285
  3. package/dist/{chunk-6ZDYMYHE.mjs → chunk-VDWTCQ74.mjs} +259 -249
  4. package/dist/cli.js +4 -4
  5. package/dist/cli.mjs +1 -1
  6. package/dist/index.d.mts +46 -134
  7. package/dist/index.d.ts +46 -134
  8. package/dist/index.js +5 -3
  9. package/dist/index.mjs +4 -2
  10. package/package.json +8 -3
  11. package/src/api/tailwindcss-patcher.ts +424 -0
  12. package/src/babel/index.ts +12 -0
  13. package/src/cache/context.ts +212 -0
  14. package/src/cache/store.ts +1440 -0
  15. package/src/cache/types.ts +71 -0
  16. package/src/cli.ts +20 -0
  17. package/src/commands/basic-handlers.ts +145 -0
  18. package/src/commands/cli.ts +56 -0
  19. package/src/commands/command-context.ts +77 -0
  20. package/src/commands/command-definitions.ts +102 -0
  21. package/src/commands/command-metadata.ts +68 -0
  22. package/src/commands/command-registrar.ts +39 -0
  23. package/src/commands/command-runtime.ts +33 -0
  24. package/src/commands/default-handler-map.ts +25 -0
  25. package/src/commands/migrate-config.ts +104 -0
  26. package/src/commands/migrate-handler.ts +67 -0
  27. package/src/commands/migration-aggregation.ts +100 -0
  28. package/src/commands/migration-args.ts +85 -0
  29. package/src/commands/migration-file-executor.ts +189 -0
  30. package/src/commands/migration-output.ts +115 -0
  31. package/src/commands/migration-report-loader.ts +26 -0
  32. package/src/commands/migration-report.ts +21 -0
  33. package/src/commands/migration-source.ts +318 -0
  34. package/src/commands/migration-target-files.ts +161 -0
  35. package/src/commands/migration-target-resolver.ts +34 -0
  36. package/src/commands/migration-types.ts +65 -0
  37. package/src/commands/restore-handler.ts +24 -0
  38. package/src/commands/status-handler.ts +17 -0
  39. package/src/commands/status-output.ts +60 -0
  40. package/src/commands/token-output.ts +30 -0
  41. package/src/commands/types.ts +137 -0
  42. package/src/commands/validate-handler.ts +42 -0
  43. package/src/commands/validate.ts +83 -0
  44. package/src/config/index.ts +25 -0
  45. package/src/config/workspace.ts +87 -0
  46. package/src/constants.ts +4 -0
  47. package/src/extraction/candidate-extractor.ts +354 -0
  48. package/src/index.ts +57 -0
  49. package/src/install/class-collector.ts +1 -0
  50. package/src/install/context-registry.ts +1 -0
  51. package/src/install/index.ts +5 -0
  52. package/src/install/patch-runner.ts +1 -0
  53. package/src/install/process-tailwindcss.ts +1 -0
  54. package/src/install/status.ts +1 -0
  55. package/src/logger.ts +5 -0
  56. package/src/options/legacy.ts +93 -0
  57. package/src/options/normalize.ts +262 -0
  58. package/src/options/types.ts +217 -0
  59. package/src/patching/operations/export-context/index.ts +110 -0
  60. package/src/patching/operations/export-context/postcss-v2.ts +235 -0
  61. package/src/patching/operations/export-context/postcss-v3.ts +249 -0
  62. package/src/patching/operations/extend-length-units.ts +197 -0
  63. package/src/patching/patch-runner.ts +46 -0
  64. package/src/patching/status.ts +262 -0
  65. package/src/runtime/class-collector.ts +105 -0
  66. package/src/runtime/collector.ts +148 -0
  67. package/src/runtime/context-registry.ts +65 -0
  68. package/src/runtime/process-tailwindcss.ts +115 -0
  69. package/src/types.ts +159 -0
  70. package/src/utils.ts +52 -0
@@ -0,0 +1,318 @@
1
+ import type { ObjectExpression, ObjectMethod, ObjectProperty } from '@babel/types'
2
+
3
+ import generate from '@babel/generator'
4
+ import { parse } from '@babel/parser'
5
+ import * as t from '@babel/types'
6
+
7
+ const ROOT_LEGACY_KEYS = ['cwd', 'overwrite', 'tailwind', 'features', 'output', 'applyPatches'] as const
8
+
9
+ type OptionObjectScope = 'root' | 'registry' | 'patch'
10
+
11
+ export interface ConfigSourceMigrationResult {
12
+ changed: boolean
13
+ code: string
14
+ changes: string[]
15
+ }
16
+
17
+ function getPropertyKeyName(property: ObjectProperty | ObjectMethod): string | undefined {
18
+ if (!property.computed && t.isIdentifier(property.key)) {
19
+ return property.key.name
20
+ }
21
+ if (t.isStringLiteral(property.key)) {
22
+ return property.key.value
23
+ }
24
+ return undefined
25
+ }
26
+
27
+ function findObjectProperty(objectExpression: ObjectExpression, name: string): ObjectProperty | undefined {
28
+ for (const property of objectExpression.properties) {
29
+ if (!t.isObjectProperty(property)) {
30
+ continue
31
+ }
32
+ if (getPropertyKeyName(property) === name) {
33
+ return property
34
+ }
35
+ }
36
+ return undefined
37
+ }
38
+
39
+ function findObjectExpressionProperty(objectExpression: ObjectExpression, name: string): ObjectExpression | undefined {
40
+ const property = findObjectProperty(objectExpression, name)
41
+ if (!property) {
42
+ return undefined
43
+ }
44
+ if (t.isObjectExpression(property.value)) {
45
+ return property.value
46
+ }
47
+ return undefined
48
+ }
49
+
50
+ function removeObjectProperty(objectExpression: ObjectExpression, property: ObjectProperty) {
51
+ const index = objectExpression.properties.indexOf(property)
52
+ if (index >= 0) {
53
+ objectExpression.properties.splice(index, 1)
54
+ }
55
+ }
56
+
57
+ function hasObjectProperty(objectExpression: ObjectExpression, name: string) {
58
+ return findObjectProperty(objectExpression, name) !== undefined
59
+ }
60
+
61
+ function keyAsIdentifier(name: string) {
62
+ return t.identifier(name)
63
+ }
64
+
65
+ function mergeObjectProperties(target: ObjectExpression, source: ObjectExpression) {
66
+ let changed = false
67
+ for (const sourceProperty of source.properties) {
68
+ if (t.isSpreadElement(sourceProperty)) {
69
+ target.properties.push(sourceProperty)
70
+ changed = true
71
+ continue
72
+ }
73
+ const sourceKey = getPropertyKeyName(sourceProperty)
74
+ if (!sourceKey) {
75
+ target.properties.push(sourceProperty)
76
+ changed = true
77
+ continue
78
+ }
79
+ if (hasObjectProperty(target, sourceKey)) {
80
+ continue
81
+ }
82
+ target.properties.push(sourceProperty)
83
+ changed = true
84
+ }
85
+ return changed
86
+ }
87
+
88
+ function moveProperty(
89
+ objectExpression: ObjectExpression,
90
+ from: string,
91
+ to: string,
92
+ changes: Set<string>,
93
+ scope: OptionObjectScope,
94
+ ) {
95
+ const source = findObjectProperty(objectExpression, from)
96
+ if (!source) {
97
+ return false
98
+ }
99
+ const target = findObjectProperty(objectExpression, to)
100
+ if (!target) {
101
+ source.key = keyAsIdentifier(to)
102
+ source.computed = false
103
+ source.shorthand = false
104
+ changes.add(`${scope}.${from} -> ${scope}.${to}`)
105
+ return true
106
+ }
107
+
108
+ if (t.isObjectExpression(source.value) && t.isObjectExpression(target.value)) {
109
+ const merged = mergeObjectProperties(target.value, source.value)
110
+ if (merged) {
111
+ changes.add(`${scope}.${from} merged into ${scope}.${to}`)
112
+ }
113
+ }
114
+ removeObjectProperty(objectExpression, source)
115
+ changes.add(`${scope}.${from} removed (preferred ${scope}.${to})`)
116
+ return true
117
+ }
118
+
119
+ function migrateExtractOptions(extract: ObjectExpression, changes: Set<string>, scope: OptionObjectScope) {
120
+ let changed = false
121
+ changed = moveProperty(extract, 'enabled', 'write', changes, scope) || changed
122
+ changed = moveProperty(extract, 'stripUniversalSelector', 'removeUniversalSelector', changes, scope) || changed
123
+ return changed
124
+ }
125
+
126
+ function migrateTailwindOptions(tailwindcss: ObjectExpression, changes: Set<string>, scope: OptionObjectScope) {
127
+ let changed = false
128
+ changed = moveProperty(tailwindcss, 'package', 'packageName', changes, scope) || changed
129
+ changed = moveProperty(tailwindcss, 'legacy', 'v2', changes, scope) || changed
130
+ changed = moveProperty(tailwindcss, 'classic', 'v3', changes, scope) || changed
131
+ changed = moveProperty(tailwindcss, 'next', 'v4', changes, scope) || changed
132
+ return changed
133
+ }
134
+
135
+ function migrateApplyOptions(apply: ObjectExpression, changes: Set<string>, scope: OptionObjectScope) {
136
+ return moveProperty(apply, 'exportContext', 'exposeContext', changes, scope)
137
+ }
138
+
139
+ function ensureObjectExpressionProperty(
140
+ objectExpression: ObjectExpression,
141
+ name: string,
142
+ changes: Set<string>,
143
+ scope: OptionObjectScope,
144
+ ) {
145
+ const existing = findObjectProperty(objectExpression, name)
146
+ if (existing) {
147
+ return t.isObjectExpression(existing.value) ? existing.value : undefined
148
+ }
149
+ const value = t.objectExpression([])
150
+ objectExpression.properties.push(t.objectProperty(keyAsIdentifier(name), value))
151
+ changes.add(`${scope}.${name} created`)
152
+ return value
153
+ }
154
+
155
+ function moveOverwriteToApply(objectExpression: ObjectExpression, changes: Set<string>, scope: OptionObjectScope) {
156
+ const overwrite = findObjectProperty(objectExpression, 'overwrite')
157
+ if (!overwrite) {
158
+ return false
159
+ }
160
+ const apply = ensureObjectExpressionProperty(objectExpression, 'apply', changes, scope)
161
+ if (!apply) {
162
+ return false
163
+ }
164
+ const hasApplyOverwrite = hasObjectProperty(apply, 'overwrite')
165
+ if (!hasApplyOverwrite) {
166
+ apply.properties.push(
167
+ t.objectProperty(keyAsIdentifier('overwrite'), overwrite.value),
168
+ )
169
+ changes.add(`${scope}.overwrite -> ${scope}.apply.overwrite`)
170
+ }
171
+ removeObjectProperty(objectExpression, overwrite)
172
+ return true
173
+ }
174
+
175
+ function hasAnyRootLegacyKeys(objectExpression: ObjectExpression) {
176
+ return ROOT_LEGACY_KEYS.some(key => hasObjectProperty(objectExpression, key))
177
+ }
178
+
179
+ function migrateOptionObject(objectExpression: ObjectExpression, scope: OptionObjectScope, changes: Set<string>) {
180
+ let changed = false
181
+ changed = moveProperty(objectExpression, 'cwd', 'projectRoot', changes, scope) || changed
182
+ changed = moveProperty(objectExpression, 'tailwind', 'tailwindcss', changes, scope) || changed
183
+ changed = moveProperty(objectExpression, 'features', 'apply', changes, scope) || changed
184
+ changed = moveProperty(objectExpression, 'applyPatches', 'apply', changes, scope) || changed
185
+ changed = moveProperty(objectExpression, 'output', 'extract', changes, scope) || changed
186
+ changed = moveOverwriteToApply(objectExpression, changes, scope) || changed
187
+
188
+ const extract = findObjectExpressionProperty(objectExpression, 'extract')
189
+ if (extract) {
190
+ changed = migrateExtractOptions(extract, changes, scope) || changed
191
+ }
192
+ const tailwindcss = findObjectExpressionProperty(objectExpression, 'tailwindcss')
193
+ if (tailwindcss) {
194
+ changed = migrateTailwindOptions(tailwindcss, changes, scope) || changed
195
+ }
196
+ const apply = findObjectExpressionProperty(objectExpression, 'apply')
197
+ if (apply) {
198
+ changed = migrateApplyOptions(apply, changes, scope) || changed
199
+ }
200
+
201
+ return changed
202
+ }
203
+
204
+ function unwrapExpression(node: t.Node): t.Node {
205
+ let current = node
206
+ while (
207
+ t.isTSAsExpression(current)
208
+ || t.isTSSatisfiesExpression(current)
209
+ || t.isTSTypeAssertion(current)
210
+ || t.isParenthesizedExpression(current)
211
+ ) {
212
+ current = current.expression
213
+ }
214
+ return current
215
+ }
216
+
217
+ function resolveObjectExpressionFromExpression(expression: t.Node): ObjectExpression | undefined {
218
+ const unwrapped = unwrapExpression(expression)
219
+ if (t.isObjectExpression(unwrapped)) {
220
+ return unwrapped
221
+ }
222
+ if (t.isCallExpression(unwrapped)) {
223
+ const [firstArg] = unwrapped.arguments
224
+ if (!firstArg || !t.isExpression(firstArg)) {
225
+ return undefined
226
+ }
227
+ const firstArgUnwrapped = unwrapExpression(firstArg)
228
+ if (t.isObjectExpression(firstArgUnwrapped)) {
229
+ return firstArgUnwrapped
230
+ }
231
+ }
232
+ return undefined
233
+ }
234
+
235
+ function resolveObjectExpressionFromProgram(program: t.Program, name: string): ObjectExpression | undefined {
236
+ for (const statement of program.body) {
237
+ if (!t.isVariableDeclaration(statement)) {
238
+ continue
239
+ }
240
+ for (const declaration of statement.declarations) {
241
+ if (!t.isIdentifier(declaration.id) || declaration.id.name !== name || !declaration.init) {
242
+ continue
243
+ }
244
+ const objectExpression = resolveObjectExpressionFromExpression(declaration.init)
245
+ if (objectExpression) {
246
+ return objectExpression
247
+ }
248
+ }
249
+ }
250
+ return undefined
251
+ }
252
+
253
+ function resolveRootConfigObjectExpression(program: t.Program): ObjectExpression | undefined {
254
+ for (const statement of program.body) {
255
+ if (!t.isExportDefaultDeclaration(statement)) {
256
+ continue
257
+ }
258
+ const declaration = statement.declaration
259
+ if (t.isIdentifier(declaration)) {
260
+ return resolveObjectExpressionFromProgram(program, declaration.name)
261
+ }
262
+ const objectExpression = resolveObjectExpressionFromExpression(declaration)
263
+ if (objectExpression) {
264
+ return objectExpression
265
+ }
266
+ }
267
+ return undefined
268
+ }
269
+
270
+ export function migrateConfigSource(source: string): ConfigSourceMigrationResult {
271
+ const ast = parse(source, {
272
+ sourceType: 'module',
273
+ plugins: ['typescript', 'jsx'],
274
+ })
275
+ const root = resolveRootConfigObjectExpression(ast.program)
276
+ if (!root) {
277
+ return {
278
+ changed: false,
279
+ code: source,
280
+ changes: [],
281
+ }
282
+ }
283
+
284
+ const changes = new Set<string>()
285
+ let changed = false
286
+
287
+ const registry = findObjectExpressionProperty(root, 'registry')
288
+ if (registry) {
289
+ changed = migrateOptionObject(registry, 'registry', changes) || changed
290
+ }
291
+
292
+ const patch = findObjectExpressionProperty(root, 'patch')
293
+ if (patch) {
294
+ changed = migrateOptionObject(patch, 'patch', changes) || changed
295
+ }
296
+
297
+ if (hasAnyRootLegacyKeys(root)) {
298
+ changed = migrateOptionObject(root, 'root', changes) || changed
299
+ }
300
+
301
+ if (!changed) {
302
+ return {
303
+ changed: false,
304
+ code: source,
305
+ changes: [],
306
+ }
307
+ }
308
+
309
+ const generated = generate(ast, {
310
+ comments: true,
311
+ }).code
312
+ const code = source.endsWith('\n') ? `${generated}\n` : generated
313
+ return {
314
+ changed: true,
315
+ code,
316
+ changes: [...changes],
317
+ }
318
+ }
@@ -0,0 +1,161 @@
1
+ import type { Dirent } from 'node:fs'
2
+
3
+ import fs from 'fs-extra'
4
+ import path from 'pathe'
5
+
6
+ export const DEFAULT_CONFIG_FILENAMES = [
7
+ 'tailwindcss-patch.config.ts',
8
+ 'tailwindcss-patch.config.js',
9
+ 'tailwindcss-patch.config.mjs',
10
+ 'tailwindcss-patch.config.cjs',
11
+ 'tailwindcss-mangle.config.ts',
12
+ 'tailwindcss-mangle.config.js',
13
+ 'tailwindcss-mangle.config.mjs',
14
+ 'tailwindcss-mangle.config.cjs',
15
+ ] as const
16
+
17
+ const DEFAULT_CONFIG_FILENAME_SET = new Set<string>(DEFAULT_CONFIG_FILENAMES)
18
+ const DEFAULT_WORKSPACE_IGNORED_DIRS = new Set([
19
+ '.git',
20
+ '.idea',
21
+ '.turbo',
22
+ '.vscode',
23
+ '.yarn',
24
+ 'coverage',
25
+ 'dist',
26
+ 'node_modules',
27
+ 'tmp',
28
+ ])
29
+ export const DEFAULT_WORKSPACE_MAX_DEPTH = 6
30
+
31
+ export function resolveTargetFiles(cwd: string, files?: string[]) {
32
+ const candidates = files && files.length > 0 ? files : [...DEFAULT_CONFIG_FILENAMES]
33
+ const resolved = new Set<string>()
34
+ for (const file of candidates) {
35
+ resolved.add(path.resolve(cwd, file))
36
+ }
37
+ return [...resolved]
38
+ }
39
+
40
+ export async function collectWorkspaceConfigFiles(cwd: string, maxDepth: number) {
41
+ const files = new Set<string>()
42
+ const queue: Array<{ dir: string, depth: number }> = [{ dir: cwd, depth: 0 }]
43
+
44
+ while (queue.length > 0) {
45
+ const current = queue.shift()
46
+ if (!current) {
47
+ continue
48
+ }
49
+ const { dir, depth } = current
50
+ let entries: Dirent[]
51
+ try {
52
+ entries = await fs.readdir(dir, { withFileTypes: true })
53
+ }
54
+ catch {
55
+ continue
56
+ }
57
+
58
+ for (const entry of entries) {
59
+ const absolutePath = path.resolve(dir, entry.name)
60
+
61
+ if (entry.isFile() && DEFAULT_CONFIG_FILENAME_SET.has(entry.name)) {
62
+ files.add(absolutePath)
63
+ continue
64
+ }
65
+
66
+ if (!entry.isDirectory()) {
67
+ continue
68
+ }
69
+ if (DEFAULT_WORKSPACE_IGNORED_DIRS.has(entry.name)) {
70
+ continue
71
+ }
72
+ if (depth >= maxDepth) {
73
+ continue
74
+ }
75
+ queue.push({ dir: absolutePath, depth: depth + 1 })
76
+ }
77
+ }
78
+
79
+ return [...files].sort((a, b) => a.localeCompare(b))
80
+ }
81
+
82
+ export function resolveBackupRelativePath(cwd: string, file: string) {
83
+ const relative = path.relative(cwd, file)
84
+ const isExternal = relative.startsWith('..') || path.isAbsolute(relative)
85
+ if (isExternal) {
86
+ const sanitized = file.replace(/[:/\\]+/g, '_')
87
+ return path.join('__external__', `${sanitized}.bak`)
88
+ }
89
+ return `${relative}.bak`
90
+ }
91
+
92
+ function normalizePattern(pattern: string) {
93
+ return pattern.replace(/\\/g, '/').replace(/^\.\/+/, '').replace(/^\/+/, '')
94
+ }
95
+
96
+ function globToRegExp(globPattern: string) {
97
+ const normalized = normalizePattern(globPattern)
98
+ let pattern = ''
99
+
100
+ for (let i = 0; i < normalized.length; i += 1) {
101
+ const char = normalized[i]!
102
+ if (char === '*') {
103
+ if (normalized[i + 1] === '*') {
104
+ pattern += '.*'
105
+ i += 1
106
+ }
107
+ else {
108
+ pattern += '[^/]*'
109
+ }
110
+ continue
111
+ }
112
+ if (char === '?') {
113
+ pattern += '[^/]'
114
+ continue
115
+ }
116
+ if ('\\^$+?.()|{}[]'.includes(char)) {
117
+ pattern += `\\${char}`
118
+ continue
119
+ }
120
+ pattern += char
121
+ }
122
+
123
+ return new RegExp(`^${pattern}$`)
124
+ }
125
+
126
+ function toPatternList(patterns?: string[]) {
127
+ if (!patterns || patterns.length === 0) {
128
+ return []
129
+ }
130
+ return patterns
131
+ .map(pattern => pattern.trim())
132
+ .filter(Boolean)
133
+ .map(globToRegExp)
134
+ }
135
+
136
+ function normalizeFileForPattern(file: string, cwd: string) {
137
+ const relative = path.relative(cwd, file)
138
+ if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
139
+ return relative.replace(/\\/g, '/')
140
+ }
141
+ return file.replace(/\\/g, '/')
142
+ }
143
+
144
+ export function filterTargetFiles(targetFiles: string[], cwd: string, include?: string[], exclude?: string[]) {
145
+ const includePatterns = toPatternList(include)
146
+ const excludePatterns = toPatternList(exclude)
147
+
148
+ if (includePatterns.length === 0 && excludePatterns.length === 0) {
149
+ return targetFiles
150
+ }
151
+
152
+ return targetFiles.filter((file) => {
153
+ const normalized = normalizeFileForPattern(file, cwd)
154
+ const inInclude = includePatterns.length === 0 || includePatterns.some(pattern => pattern.test(normalized))
155
+ if (!inInclude) {
156
+ return false
157
+ }
158
+ const inExclude = excludePatterns.some(pattern => pattern.test(normalized))
159
+ return !inExclude
160
+ })
161
+ }
@@ -0,0 +1,34 @@
1
+ import type { MigrateConfigFilesOptions } from './migration-types'
2
+ import {
3
+ collectWorkspaceConfigFiles,
4
+ DEFAULT_WORKSPACE_MAX_DEPTH,
5
+ filterTargetFiles,
6
+ resolveTargetFiles,
7
+ } from './migration-target-files'
8
+
9
+ export interface ResolveMigrationTargetFilesOptions {
10
+ cwd: string
11
+ files?: MigrateConfigFilesOptions['files']
12
+ workspace?: MigrateConfigFilesOptions['workspace']
13
+ maxDepth?: MigrateConfigFilesOptions['maxDepth']
14
+ include?: MigrateConfigFilesOptions['include']
15
+ exclude?: MigrateConfigFilesOptions['exclude']
16
+ }
17
+
18
+ export async function resolveMigrationTargetFiles(options: ResolveMigrationTargetFilesOptions) {
19
+ const {
20
+ cwd,
21
+ files,
22
+ workspace,
23
+ maxDepth,
24
+ include,
25
+ exclude,
26
+ } = options
27
+ const resolvedMaxDepth = maxDepth ?? DEFAULT_WORKSPACE_MAX_DEPTH
28
+ const discoveredTargetFiles = files && files.length > 0
29
+ ? resolveTargetFiles(cwd, files)
30
+ : workspace
31
+ ? await collectWorkspaceConfigFiles(cwd, resolvedMaxDepth)
32
+ : resolveTargetFiles(cwd)
33
+ return filterTargetFiles(discoveredTargetFiles, cwd, include, exclude)
34
+ }
@@ -0,0 +1,65 @@
1
+ import type { MIGRATION_REPORT_KIND, MIGRATION_REPORT_SCHEMA_VERSION } from './migration-report'
2
+
3
+ export interface ConfigFileMigrationEntry {
4
+ file: string
5
+ changed: boolean
6
+ written: boolean
7
+ rolledBack: boolean
8
+ backupFile?: string
9
+ changes: string[]
10
+ }
11
+
12
+ export interface ConfigFileMigrationReport {
13
+ reportKind: typeof MIGRATION_REPORT_KIND
14
+ schemaVersion: typeof MIGRATION_REPORT_SCHEMA_VERSION
15
+ generatedAt: string
16
+ tool: {
17
+ name: string
18
+ version: string
19
+ }
20
+ cwd: string
21
+ dryRun: boolean
22
+ rollbackOnError: boolean
23
+ backupDirectory?: string
24
+ scannedFiles: number
25
+ changedFiles: number
26
+ writtenFiles: number
27
+ backupsWritten: number
28
+ unchangedFiles: number
29
+ missingFiles: number
30
+ entries: ConfigFileMigrationEntry[]
31
+ }
32
+
33
+ export interface MigrateConfigFilesOptions {
34
+ cwd: string
35
+ files?: string[]
36
+ dryRun?: boolean
37
+ workspace?: boolean
38
+ maxDepth?: number
39
+ rollbackOnError?: boolean
40
+ backupDir?: string
41
+ include?: string[]
42
+ exclude?: string[]
43
+ }
44
+
45
+ export interface RestoreConfigFilesOptions {
46
+ cwd: string
47
+ reportFile: string
48
+ dryRun?: boolean
49
+ strict?: boolean
50
+ }
51
+
52
+ export interface RestoreConfigFilesResult {
53
+ cwd: string
54
+ reportFile: string
55
+ reportKind?: string
56
+ reportSchemaVersion?: number
57
+ dryRun: boolean
58
+ strict: boolean
59
+ scannedEntries: number
60
+ restorableEntries: number
61
+ restoredFiles: number
62
+ missingBackups: number
63
+ skippedEntries: number
64
+ restored: string[]
65
+ }
@@ -0,0 +1,24 @@
1
+ import type { TailwindcssPatchCommandContext } from './types'
2
+
3
+ import { restoreConfigFiles } from './migrate-config'
4
+ import { resolveRestoreCommandArgs } from './migration-args'
5
+ import { logRestoreResultAsJson, logRestoreSummary } from './migration-output'
6
+
7
+ export async function restoreCommandDefaultHandler(ctx: TailwindcssPatchCommandContext<'restore'>) {
8
+ const { args } = ctx
9
+ const restoreArgs = resolveRestoreCommandArgs(args)
10
+ const result = await restoreConfigFiles({
11
+ cwd: ctx.cwd,
12
+ reportFile: restoreArgs.reportFile,
13
+ dryRun: restoreArgs.dryRun,
14
+ strict: restoreArgs.strict,
15
+ })
16
+
17
+ if (args.json) {
18
+ logRestoreResultAsJson(result)
19
+ return result
20
+ }
21
+
22
+ logRestoreSummary(result)
23
+ return result
24
+ }
@@ -0,0 +1,17 @@
1
+ import type { TailwindcssPatchCommandContext } from './types'
2
+
3
+ import { logStatusReportAsJson, logStatusReportSummary } from './status-output'
4
+
5
+ export async function statusCommandDefaultHandler(ctx: TailwindcssPatchCommandContext<'status'>) {
6
+ const patcher = await ctx.createPatcher()
7
+ const report = await patcher.getPatchStatus()
8
+
9
+ if (ctx.args.json) {
10
+ logStatusReportAsJson(report)
11
+ return report
12
+ }
13
+
14
+ logStatusReportSummary(report)
15
+
16
+ return report
17
+ }
@@ -0,0 +1,60 @@
1
+ import type { PatchStatusReport } from '../types'
2
+
3
+ import logger from '../logger'
4
+
5
+ function formatFilesHint(entry: PatchStatusReport['entries'][number]) {
6
+ if (!entry.files.length) {
7
+ return ''
8
+ }
9
+ return ` (${entry.files.join(', ')})`
10
+ }
11
+
12
+ function formatPackageLabel(report: PatchStatusReport) {
13
+ return `${report.package.name ?? 'tailwindcss'}@${report.package.version ?? 'unknown'}`
14
+ }
15
+
16
+ function partitionStatusEntries(report: PatchStatusReport) {
17
+ return {
18
+ applied: report.entries.filter(entry => entry.status === 'applied'),
19
+ pending: report.entries.filter(entry => entry.status === 'not-applied'),
20
+ skipped: report.entries.filter(entry => entry.status === 'skipped' || entry.status === 'unsupported'),
21
+ }
22
+ }
23
+
24
+ export function logStatusReportAsJson(report: PatchStatusReport) {
25
+ logger.log(JSON.stringify(report, null, 2))
26
+ }
27
+
28
+ export function logStatusReportSummary(report: PatchStatusReport) {
29
+ const {
30
+ applied,
31
+ pending,
32
+ skipped,
33
+ } = partitionStatusEntries(report)
34
+
35
+ logger.info(`Patch status for ${formatPackageLabel(report)} (v${report.majorVersion})`)
36
+
37
+ if (applied.length) {
38
+ logger.success('Applied:')
39
+ applied.forEach(entry => logger.success(` • ${entry.name}${formatFilesHint(entry)}`))
40
+ }
41
+
42
+ if (pending.length) {
43
+ logger.warn('Needs attention:')
44
+ pending.forEach((entry) => {
45
+ const details = entry.reason ? ` - ${entry.reason}` : ''
46
+ logger.warn(` • ${entry.name}${formatFilesHint(entry)}${details}`)
47
+ })
48
+ }
49
+ else {
50
+ logger.success('All applicable patches are applied.')
51
+ }
52
+
53
+ if (skipped.length) {
54
+ logger.info('Skipped:')
55
+ skipped.forEach((entry) => {
56
+ const details = entry.reason ? ` - ${entry.reason}` : ''
57
+ logger.info(` • ${entry.name}${details}`)
58
+ })
59
+ }
60
+ }