tailwindcss-patch 9.0.0-alpha.1 → 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 +20 -0
  2. package/dist/{chunk-Z6OMJZTU.js → chunk-TOAZIPHJ.js} +29 -11
  3. package/dist/{chunk-SWLOK2S6.mjs → chunk-VDWTCQ74.mjs} +29 -11
  4. package/dist/cli.js +4 -4
  5. package/dist/cli.mjs +1 -1
  6. package/dist/index.d.mts +43 -35
  7. package/dist/index.d.ts +43 -35
  8. package/dist/index.js +2 -2
  9. package/dist/index.mjs +1 -1
  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,235 @@
1
+ import type { ExposeContextTransformOptions } from './postcss-v3'
2
+ import * as t from '@babel/types'
3
+ import { generate, parse, traverse } from '../../../babel'
4
+
5
+ const IDENTIFIER_RE = /^[A-Z_$][\w$]*$/i
6
+
7
+ function toIdentifierName(property: string) {
8
+ if (!property) {
9
+ return 'contextRef'
10
+ }
11
+ const sanitized = property.replace(/[^\w$]/gu, '_')
12
+ if (/^\d/.test(sanitized)) {
13
+ return `_${sanitized}`
14
+ }
15
+ return sanitized || 'contextRef'
16
+ }
17
+
18
+ function createExportsMember(property: string) {
19
+ if (IDENTIFIER_RE.test(property)) {
20
+ return t.memberExpression(t.identifier('exports'), t.identifier(property))
21
+ }
22
+ return t.memberExpression(t.identifier('exports'), t.stringLiteral(property), true)
23
+ }
24
+
25
+ export function transformProcessTailwindFeaturesReturnContextV2(content: string) {
26
+ const ast = parse(content, {
27
+ sourceType: 'unambiguous',
28
+ })
29
+ let hasPatched = false
30
+
31
+ traverse(ast, {
32
+ FunctionDeclaration(path) {
33
+ const node = path.node
34
+ if (
35
+ node.id?.name !== 'processTailwindFeatures'
36
+ || node.body.body.length !== 1
37
+ || !t.isReturnStatement(node.body.body[0])
38
+ ) {
39
+ return
40
+ }
41
+
42
+ const returnStatement = node.body.body[0]
43
+ if (!t.isFunctionExpression(returnStatement.argument)) {
44
+ return
45
+ }
46
+
47
+ const body = returnStatement.argument.body.body
48
+ const lastStatement = body[body.length - 1]
49
+ const alreadyReturnsContext = Boolean(
50
+ t.isReturnStatement(lastStatement)
51
+ && t.isIdentifier(lastStatement.argument)
52
+ && lastStatement.argument.name === 'context',
53
+ )
54
+
55
+ hasPatched = alreadyReturnsContext
56
+ if (!alreadyReturnsContext) {
57
+ body.push(t.returnStatement(t.identifier('context')))
58
+ }
59
+ },
60
+ })
61
+
62
+ return {
63
+ code: hasPatched ? content : generate(ast).code,
64
+ hasPatched,
65
+ }
66
+ }
67
+
68
+ export function transformPostcssPluginV2(content: string, options: ExposeContextTransformOptions) {
69
+ const refIdentifier = t.identifier(toIdentifierName(options.refProperty))
70
+ const exportMember = createExportsMember(options.refProperty)
71
+ const valueMember = t.memberExpression(refIdentifier, t.identifier('value'))
72
+ const ast = parse(content)
73
+
74
+ let hasPatched = false
75
+
76
+ traverse(ast, {
77
+ Program(path) {
78
+ const program = path.node
79
+ const index = program.body.findIndex((statement) => {
80
+ return t.isFunctionDeclaration(statement) && statement.id?.name === '_default'
81
+ })
82
+
83
+ if (index === -1) {
84
+ return
85
+ }
86
+
87
+ const previous = program.body[index - 1]
88
+ const beforePrevious = program.body[index - 2]
89
+
90
+ const alreadyHasVariable = Boolean(
91
+ previous
92
+ && t.isVariableDeclaration(previous)
93
+ && previous.declarations.length === 1
94
+ && (() => {
95
+ const declaration = previous.declarations[0]
96
+ return Boolean(
97
+ declaration
98
+ && t.isIdentifier(declaration.id)
99
+ && declaration.id.name === refIdentifier.name,
100
+ )
101
+ })(),
102
+ )
103
+
104
+ const alreadyAssignsExports = Boolean(
105
+ beforePrevious
106
+ && t.isExpressionStatement(beforePrevious)
107
+ && t.isAssignmentExpression(beforePrevious.expression)
108
+ && t.isMemberExpression(beforePrevious.expression.left)
109
+ && t.isIdentifier(beforePrevious.expression.right)
110
+ && beforePrevious.expression.right.name === refIdentifier.name
111
+ && generate(beforePrevious.expression.left).code === generate(exportMember).code,
112
+ )
113
+
114
+ hasPatched = alreadyHasVariable && alreadyAssignsExports
115
+
116
+ if (!alreadyHasVariable) {
117
+ program.body.splice(
118
+ index,
119
+ 0,
120
+ t.variableDeclaration('var', [
121
+ t.variableDeclarator(
122
+ refIdentifier,
123
+ t.objectExpression([
124
+ t.objectProperty(t.identifier('value'), t.arrayExpression()),
125
+ ]),
126
+ ),
127
+ ]),
128
+ t.expressionStatement(
129
+ t.assignmentExpression('=', exportMember, refIdentifier),
130
+ ),
131
+ )
132
+ }
133
+ },
134
+ FunctionDeclaration(path) {
135
+ if (hasPatched) {
136
+ return
137
+ }
138
+
139
+ const fn = path.node
140
+ if (fn.id?.name !== '_default') {
141
+ return
142
+ }
143
+
144
+ if (fn.body.body.length !== 1 || !t.isReturnStatement(fn.body.body[0])) {
145
+ return
146
+ }
147
+
148
+ const returnStatement = fn.body.body[0]
149
+ if (
150
+ !t.isCallExpression(returnStatement.argument)
151
+ || !t.isMemberExpression(returnStatement.argument.callee)
152
+ || !t.isArrayExpression(returnStatement.argument.callee.object)
153
+ ) {
154
+ return
155
+ }
156
+
157
+ const fnExpression = returnStatement.argument.callee.object.elements[1]
158
+ if (!fnExpression || !t.isFunctionExpression(fnExpression)) {
159
+ return
160
+ }
161
+
162
+ const block = fnExpression.body
163
+ const statements = block.body
164
+
165
+ if (
166
+ t.isExpressionStatement(statements[0])
167
+ && t.isAssignmentExpression(statements[0].expression)
168
+ && t.isNumericLiteral(statements[0].expression.right)
169
+ ) {
170
+ hasPatched = true
171
+ return
172
+ }
173
+
174
+ const lastStatement = statements[statements.length - 1]
175
+ if (
176
+ lastStatement
177
+ && t.isExpressionStatement(lastStatement)
178
+ ) {
179
+ statements[statements.length - 1] = t.expressionStatement(
180
+ t.callExpression(
181
+ t.memberExpression(valueMember, t.identifier('push')),
182
+ [lastStatement.expression],
183
+ ),
184
+ )
185
+ }
186
+
187
+ const index = statements.findIndex(statement => t.isIfStatement(statement))
188
+ if (index > -1) {
189
+ const ifStatement = statements[index] as t.IfStatement
190
+ if (
191
+ t.isBlockStatement(ifStatement.consequent)
192
+ && ifStatement.consequent.body[1]
193
+ && t.isForOfStatement(ifStatement.consequent.body[1])
194
+ ) {
195
+ const forOf = ifStatement.consequent.body[1]
196
+ if (
197
+ t.isBlockStatement(forOf.body)
198
+ && forOf.body.body.length === 1
199
+ ) {
200
+ const nestedIf = forOf.body.body[0]
201
+ if (
202
+ nestedIf
203
+ && t.isIfStatement(nestedIf)
204
+ && t.isBlockStatement(nestedIf.consequent)
205
+ && nestedIf.consequent.body.length === 1
206
+ && t.isExpressionStatement(nestedIf.consequent.body[0])
207
+ ) {
208
+ nestedIf.consequent.body[0] = t.expressionStatement(
209
+ t.callExpression(
210
+ t.memberExpression(valueMember, t.identifier('push')),
211
+ [nestedIf.consequent.body[0].expression],
212
+ ),
213
+ )
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ statements.unshift(
220
+ t.expressionStatement(
221
+ t.assignmentExpression(
222
+ '=',
223
+ t.memberExpression(valueMember, t.identifier('length')),
224
+ t.numericLiteral(0),
225
+ ),
226
+ ),
227
+ )
228
+ },
229
+ })
230
+
231
+ return {
232
+ code: hasPatched ? content : generate(ast).code,
233
+ hasPatched,
234
+ }
235
+ }
@@ -0,0 +1,249 @@
1
+ import * as t from '@babel/types'
2
+ import { generate, parse, traverse } from '../../../babel'
3
+
4
+ export interface ExposeContextTransformOptions {
5
+ refProperty: string
6
+ }
7
+
8
+ const IDENTIFIER_RE = /^[A-Z_$][\w$]*$/i
9
+
10
+ function toIdentifierName(property: string) {
11
+ if (!property) {
12
+ return 'contextRef'
13
+ }
14
+ const sanitized = property.replace(/[^\w$]/gu, '_')
15
+ if (/^\d/.test(sanitized)) {
16
+ return `_${sanitized}`
17
+ }
18
+ return sanitized || 'contextRef'
19
+ }
20
+
21
+ function createModuleExportsMember(property: string) {
22
+ const object = t.memberExpression(t.identifier('module'), t.identifier('exports'))
23
+ if (IDENTIFIER_RE.test(property)) {
24
+ return t.memberExpression(object, t.identifier(property))
25
+ }
26
+ return t.memberExpression(object, t.stringLiteral(property), true)
27
+ }
28
+
29
+ export function transformProcessTailwindFeaturesReturnContext(content: string) {
30
+ const ast = parse(content)
31
+ let hasPatched = false
32
+
33
+ traverse(ast, {
34
+ FunctionDeclaration(path) {
35
+ const node = path.node
36
+ if (
37
+ node.id?.name !== 'processTailwindFeatures'
38
+ || node.body.body.length !== 1
39
+ ) {
40
+ return
41
+ }
42
+
43
+ const [returnStatement] = node.body.body
44
+ if (
45
+ !t.isReturnStatement(returnStatement)
46
+ || !t.isFunctionExpression(returnStatement.argument)
47
+ ) {
48
+ return
49
+ }
50
+
51
+ const expression = returnStatement.argument
52
+ const body = expression.body.body
53
+ const lastStatement = body[body.length - 1]
54
+ const alreadyReturnsContext = Boolean(
55
+ t.isReturnStatement(lastStatement)
56
+ && t.isIdentifier(lastStatement.argument)
57
+ && lastStatement.argument.name === 'context',
58
+ )
59
+
60
+ hasPatched = alreadyReturnsContext
61
+ if (!alreadyReturnsContext) {
62
+ body.push(t.returnStatement(t.identifier('context')))
63
+ }
64
+ },
65
+ })
66
+
67
+ return {
68
+ code: hasPatched ? content : generate(ast).code,
69
+ hasPatched,
70
+ }
71
+ }
72
+
73
+ export function transformPostcssPlugin(content: string, { refProperty }: ExposeContextTransformOptions) {
74
+ const ast = parse(content)
75
+ const refIdentifier = t.identifier(toIdentifierName(refProperty))
76
+ const moduleExportsMember = createModuleExportsMember(refProperty)
77
+ const valueMember = t.memberExpression(refIdentifier, t.identifier('value'))
78
+
79
+ let hasPatched = false
80
+
81
+ traverse(ast, {
82
+ Program(path) {
83
+ const program = path.node
84
+ const index = program.body.findIndex((statement) => {
85
+ return (
86
+ t.isExpressionStatement(statement)
87
+ && t.isAssignmentExpression(statement.expression)
88
+ && t.isMemberExpression(statement.expression.left)
89
+ && t.isFunctionExpression(statement.expression.right)
90
+ && statement.expression.right.id?.name === 'tailwindcss'
91
+ )
92
+ })
93
+
94
+ if (index === -1) {
95
+ return
96
+ }
97
+
98
+ const previousStatement = program.body[index - 1]
99
+ const lastStatement = program.body[program.body.length - 1]
100
+ const alreadyHasVariable = Boolean(
101
+ previousStatement
102
+ && t.isVariableDeclaration(previousStatement)
103
+ && previousStatement.declarations.length === 1
104
+ && (() => {
105
+ const declaration = previousStatement.declarations[0]
106
+ return Boolean(
107
+ declaration
108
+ && t.isIdentifier(declaration.id)
109
+ && declaration.id.name === refIdentifier.name,
110
+ )
111
+ })(),
112
+ )
113
+
114
+ const alreadyAssignsModuleExports = Boolean(
115
+ t.isExpressionStatement(lastStatement)
116
+ && t.isAssignmentExpression(lastStatement.expression)
117
+ && t.isMemberExpression(lastStatement.expression.left)
118
+ && t.isIdentifier(lastStatement.expression.right)
119
+ && lastStatement.expression.right.name === refIdentifier.name
120
+ && generate(lastStatement.expression.left).code === generate(moduleExportsMember).code,
121
+ )
122
+
123
+ hasPatched = alreadyHasVariable && alreadyAssignsModuleExports
124
+
125
+ if (!alreadyHasVariable) {
126
+ program.body.splice(
127
+ index,
128
+ 0,
129
+ t.variableDeclaration('const', [
130
+ t.variableDeclarator(
131
+ refIdentifier,
132
+ t.objectExpression([
133
+ t.objectProperty(t.identifier('value'), t.arrayExpression()),
134
+ ]),
135
+ ),
136
+ ]),
137
+ )
138
+ }
139
+
140
+ if (!alreadyAssignsModuleExports) {
141
+ program.body.push(
142
+ t.expressionStatement(
143
+ t.assignmentExpression('=', moduleExportsMember, refIdentifier),
144
+ ),
145
+ )
146
+ }
147
+ },
148
+ FunctionExpression(path) {
149
+ if (hasPatched) {
150
+ return
151
+ }
152
+ const fn = path.node
153
+ if (fn.id?.name !== 'tailwindcss' || fn.body.body.length !== 1) {
154
+ return
155
+ }
156
+
157
+ const [returnStatement] = fn.body.body
158
+ if (!returnStatement || !t.isReturnStatement(returnStatement) || !t.isObjectExpression(returnStatement.argument)) {
159
+ return
160
+ }
161
+
162
+ const properties = returnStatement.argument.properties
163
+ if (properties.length !== 2) {
164
+ return
165
+ }
166
+
167
+ const pluginsProperty = properties.find(
168
+ prop => t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === 'plugins',
169
+ )
170
+
171
+ if (
172
+ !pluginsProperty
173
+ || !t.isObjectProperty(pluginsProperty)
174
+ || !t.isCallExpression(pluginsProperty.value)
175
+ || !t.isMemberExpression(pluginsProperty.value.callee)
176
+ || !t.isArrayExpression(pluginsProperty.value.callee.object)
177
+ ) {
178
+ return
179
+ }
180
+
181
+ const pluginsArray = pluginsProperty.value.callee.object.elements
182
+
183
+ const targetPlugin = pluginsArray[1]
184
+ if (!targetPlugin || !t.isFunctionExpression(targetPlugin)) {
185
+ return
186
+ }
187
+
188
+ const block = targetPlugin.body
189
+ const statements = block.body
190
+ const last = statements[statements.length - 1]
191
+
192
+ if (
193
+ last
194
+ && t.isExpressionStatement(last)
195
+ ) {
196
+ statements[statements.length - 1] = t.expressionStatement(
197
+ t.callExpression(
198
+ t.memberExpression(valueMember, t.identifier('push')),
199
+ [last.expression],
200
+ ),
201
+ )
202
+ }
203
+
204
+ const index = statements.findIndex(s => t.isIfStatement(s))
205
+ if (index > -1) {
206
+ const ifStatement = statements[index] as t.IfStatement
207
+ if (t.isBlockStatement(ifStatement.consequent)) {
208
+ const [, second] = ifStatement.consequent.body
209
+ if (
210
+ second
211
+ && t.isForOfStatement(second)
212
+ && t.isBlockStatement(second.body)
213
+ ) {
214
+ const bodyStatement = second.body.body[0]
215
+ if (
216
+ bodyStatement
217
+ && t.isIfStatement(bodyStatement)
218
+ && t.isBlockStatement(bodyStatement.consequent)
219
+ && bodyStatement.consequent.body.length === 1
220
+ && t.isExpressionStatement(bodyStatement.consequent.body[0])
221
+ ) {
222
+ bodyStatement.consequent.body[0] = t.expressionStatement(
223
+ t.callExpression(
224
+ t.memberExpression(valueMember, t.identifier('push')),
225
+ [bodyStatement.consequent.body[0].expression],
226
+ ),
227
+ )
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ statements.unshift(
234
+ t.expressionStatement(
235
+ t.assignmentExpression(
236
+ '=',
237
+ t.memberExpression(valueMember, t.identifier('length')),
238
+ t.numericLiteral(0),
239
+ ),
240
+ ),
241
+ )
242
+ },
243
+ })
244
+
245
+ return {
246
+ code: hasPatched ? content : generate(ast).code,
247
+ hasPatched,
248
+ }
249
+ }
@@ -0,0 +1,197 @@
1
+ import type { ArrayExpression, StringLiteral } from '@babel/types'
2
+ import type { NormalizedExtendLengthUnitsOptions } from '../../options/types'
3
+ import * as t from '@babel/types'
4
+ import fs from 'fs-extra'
5
+ import path from 'pathe'
6
+ import { generate, parse, traverse } from '../../babel'
7
+ import logger from '../../logger'
8
+ import { spliceChangesIntoString } from '../../utils'
9
+
10
+ interface FindArrayExpressionResult {
11
+ arrayRef?: ArrayExpression
12
+ changed: boolean
13
+ }
14
+
15
+ function updateLengthUnitsArray(content: string, options: NormalizedExtendLengthUnitsOptions): FindArrayExpressionResult {
16
+ const { variableName = 'lengthUnits', units } = options
17
+ const ast = parse(content)
18
+
19
+ let arrayRef: ArrayExpression | undefined
20
+ let changed = false
21
+
22
+ traverse(ast, {
23
+ Identifier(path) {
24
+ if (
25
+ path.node.name === variableName
26
+ && t.isVariableDeclarator(path.parent)
27
+ && t.isArrayExpression(path.parent.init)
28
+ ) {
29
+ arrayRef = path.parent.init
30
+ const existing = new Set(
31
+ path.parent.init.elements.map(element => (t.isStringLiteral(element) ? (element as StringLiteral).value : undefined)).filter(Boolean),
32
+ )
33
+
34
+ for (const unit of units) {
35
+ if (!existing.has(unit)) {
36
+ path.parent.init.elements = path.parent.init.elements.map((element) => {
37
+ if (t.isStringLiteral(element)) {
38
+ return t.stringLiteral(element.value)
39
+ }
40
+ return element
41
+ })
42
+ path.parent.init.elements.push(t.stringLiteral(unit))
43
+ changed = true
44
+ }
45
+ }
46
+ }
47
+ },
48
+ })
49
+
50
+ return {
51
+ ...(arrayRef === undefined ? {} : { arrayRef }),
52
+ changed,
53
+ }
54
+ }
55
+
56
+ export function applyExtendLengthUnitsPatchV3(rootDir: string, options: NormalizedExtendLengthUnitsOptions) {
57
+ if (!options.enabled) {
58
+ return { changed: false, code: undefined }
59
+ }
60
+
61
+ const opts: NormalisedV3Options = {
62
+ ...options,
63
+ lengthUnitsFilePath: options.lengthUnitsFilePath ?? 'lib/util/dataTypes.js',
64
+ variableName: options.variableName ?? 'lengthUnits',
65
+ }
66
+
67
+ const dataTypesFilePath = path.resolve(rootDir, opts.lengthUnitsFilePath)
68
+ const exists = fs.existsSync(dataTypesFilePath)
69
+
70
+ if (!exists) {
71
+ return { changed: false, code: undefined }
72
+ }
73
+
74
+ const content = fs.readFileSync(dataTypesFilePath, 'utf8')
75
+ const { arrayRef, changed } = updateLengthUnitsArray(content, opts)
76
+
77
+ if (!arrayRef || !changed) {
78
+ return { changed: false, code: undefined }
79
+ }
80
+
81
+ const { code } = generate(arrayRef, {
82
+ jsescOption: { quotes: 'single' },
83
+ })
84
+
85
+ if (arrayRef.start != null && arrayRef.end != null) {
86
+ const nextCode = `${content.slice(0, arrayRef.start)}${code}${content.slice(arrayRef.end)}`
87
+ if (opts.overwrite) {
88
+ const target = opts.destPath ? path.resolve(opts.destPath) : dataTypesFilePath
89
+ fs.writeFileSync(target, nextCode, 'utf8')
90
+ logger.success('Patched Tailwind CSS length unit list (v3).')
91
+ }
92
+
93
+ return {
94
+ changed: true,
95
+ code: nextCode,
96
+ }
97
+ }
98
+
99
+ return {
100
+ changed: false,
101
+ code: undefined,
102
+ }
103
+ }
104
+
105
+ interface NormalisedV3Options extends NormalizedExtendLengthUnitsOptions {
106
+ lengthUnitsFilePath: string
107
+ variableName: string
108
+ }
109
+
110
+ interface V4FilePatch {
111
+ file: string
112
+ code: string
113
+ hasPatched: boolean
114
+ }
115
+
116
+ interface V4Candidate extends V4FilePatch {
117
+ match: RegExpExecArray
118
+ }
119
+
120
+ export function applyExtendLengthUnitsPatchV4(rootDir: string, options: NormalizedExtendLengthUnitsOptions) {
121
+ if (!options.enabled) {
122
+ return { files: [], changed: false }
123
+ }
124
+
125
+ const opts: NormalisedV4Options = { ...options }
126
+
127
+ const distDir = path.resolve(rootDir, 'dist')
128
+ if (!fs.existsSync(distDir)) {
129
+ return { files: [], changed: false }
130
+ }
131
+
132
+ const entries = fs.readdirSync(distDir)
133
+ const chunkNames = entries.filter(entry => entry.endsWith('.js') || entry.endsWith('.mjs'))
134
+ const pattern = /\[\s*["']cm["'],\s*["']mm["'],[\w,"']+\]/
135
+
136
+ const candidates = chunkNames.map((chunkName) => {
137
+ const file = path.join(distDir, chunkName)
138
+ const code = fs.readFileSync(file, 'utf8')
139
+ const match = pattern.exec(code)
140
+ if (!match) {
141
+ return null
142
+ }
143
+ return {
144
+ file,
145
+ code,
146
+ match,
147
+ hasPatched: false,
148
+ }
149
+ }).filter((candidate): candidate is V4Candidate => candidate !== null)
150
+
151
+ for (const item of candidates) {
152
+ const { code, file, match } = item
153
+ const ast = parse(match[0], { sourceType: 'unambiguous' })
154
+
155
+ traverse(ast, {
156
+ ArrayExpression(path) {
157
+ for (const unit of opts.units) {
158
+ if (path.node.elements.some(element => t.isStringLiteral(element) && element.value === unit)) {
159
+ item.hasPatched = true
160
+ return
161
+ }
162
+ path.node.elements.push(t.stringLiteral(unit))
163
+ }
164
+ },
165
+ })
166
+
167
+ if (item.hasPatched) {
168
+ continue
169
+ }
170
+
171
+ const { code: replacement } = generate(ast, { minified: true })
172
+ const start = match.index ?? 0
173
+ const end = start + match[0].length
174
+ item.code = spliceChangesIntoString(code, [
175
+ {
176
+ start,
177
+ end,
178
+ replacement: replacement.endsWith(';') ? replacement.slice(0, -1) : replacement,
179
+ },
180
+ ])
181
+
182
+ if (opts.overwrite) {
183
+ fs.writeFileSync(file, item.code, 'utf8')
184
+ }
185
+ }
186
+
187
+ if (candidates.some(file => !file.hasPatched)) {
188
+ logger.success('Patched Tailwind CSS length unit list (v4).')
189
+ }
190
+
191
+ return {
192
+ changed: candidates.some(file => !file.hasPatched),
193
+ files: candidates,
194
+ }
195
+ }
196
+
197
+ interface NormalisedV4Options extends NormalizedExtendLengthUnitsOptions {}