tailwindcss-patch 9.0.0-alpha.1 → 9.0.0-alpha.3
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 +20 -0
- package/dist/{chunk-Z6OMJZTU.js → chunk-77GHKSKG.js} +59 -732
- package/dist/{chunk-SWLOK2S6.mjs → chunk-D6ICWMM4.mjs} +63 -736
- package/dist/chunk-PMN7HS4Y.js +25 -0
- package/dist/chunk-YYBY7EM5.mjs +21 -0
- package/dist/cli.js +7 -5
- package/dist/cli.mjs +5 -3
- package/dist/commands/cli-runtime.d.mts +13 -0
- package/dist/commands/cli-runtime.d.ts +13 -0
- package/dist/commands/cli-runtime.js +1331 -0
- package/dist/commands/cli-runtime.mjs +1331 -0
- package/dist/index.d.mts +17 -677
- package/dist/index.d.ts +17 -677
- package/dist/index.js +5 -7
- package/dist/index.mjs +6 -8
- package/dist/validate-nbmOI2w8.d.mts +677 -0
- package/dist/validate-nbmOI2w8.d.ts +677 -0
- package/package.json +11 -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.bundle.ts +20 -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.bundle.ts +105 -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,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
|
+
}
|