1- import type { ObjectExpression , ObjectMethod , ObjectProperty } from '@babel/types'
2- import generate from '@babel/generator'
3- import { parse } from '@babel/parser'
4- import * as t from '@babel/types'
51import fs from 'fs-extra'
62import path from 'pathe'
73import { pkgName , pkgVersion } from '../constants'
@@ -17,18 +13,11 @@ import {
1713 MIGRATION_REPORT_KIND ,
1814 MIGRATION_REPORT_SCHEMA_VERSION ,
1915} from './migration-report'
16+ import { migrateConfigSource } from './migration-source'
2017export { DEFAULT_CONFIG_FILENAMES } from './migration-target-files'
2118export { MIGRATION_REPORT_KIND , MIGRATION_REPORT_SCHEMA_VERSION } from './migration-report'
22-
23- const ROOT_LEGACY_KEYS = [ 'cwd' , 'overwrite' , 'tailwind' , 'features' , 'output' , 'applyPatches' ] as const
24-
25- type OptionObjectScope = 'root' | 'registry' | 'patch'
26-
27- export interface ConfigSourceMigrationResult {
28- changed : boolean
29- code : string
30- changes : string [ ]
31- }
19+ export { migrateConfigSource } from './migration-source'
20+ export type { ConfigSourceMigrationResult } from './migration-source'
3221
3322export interface ConfigFileMigrationEntry {
3423 file : string
@@ -94,309 +83,6 @@ export interface RestoreConfigFilesResult {
9483 restored : string [ ]
9584}
9685
97- function getPropertyKeyName ( property : ObjectProperty | ObjectMethod ) : string | undefined {
98- if ( ! property . computed && t . isIdentifier ( property . key ) ) {
99- return property . key . name
100- }
101- if ( t . isStringLiteral ( property . key ) ) {
102- return property . key . value
103- }
104- return undefined
105- }
106-
107- function findObjectProperty ( objectExpression : ObjectExpression , name : string ) : ObjectProperty | undefined {
108- for ( const property of objectExpression . properties ) {
109- if ( ! t . isObjectProperty ( property ) ) {
110- continue
111- }
112- if ( getPropertyKeyName ( property ) === name ) {
113- return property
114- }
115- }
116- return undefined
117- }
118-
119- function findObjectExpressionProperty ( objectExpression : ObjectExpression , name : string ) : ObjectExpression | undefined {
120- const property = findObjectProperty ( objectExpression , name )
121- if ( ! property ) {
122- return undefined
123- }
124- if ( t . isObjectExpression ( property . value ) ) {
125- return property . value
126- }
127- return undefined
128- }
129-
130- function removeObjectProperty ( objectExpression : ObjectExpression , property : ObjectProperty ) {
131- const index = objectExpression . properties . indexOf ( property )
132- if ( index >= 0 ) {
133- objectExpression . properties . splice ( index , 1 )
134- }
135- }
136-
137- function hasObjectProperty ( objectExpression : ObjectExpression , name : string ) {
138- return findObjectProperty ( objectExpression , name ) !== undefined
139- }
140-
141- function keyAsIdentifier ( name : string ) {
142- return t . identifier ( name )
143- }
144-
145- function mergeObjectProperties ( target : ObjectExpression , source : ObjectExpression ) {
146- let changed = false
147- for ( const sourceProperty of source . properties ) {
148- if ( t . isSpreadElement ( sourceProperty ) ) {
149- target . properties . push ( sourceProperty )
150- changed = true
151- continue
152- }
153- const sourceKey = getPropertyKeyName ( sourceProperty )
154- if ( ! sourceKey ) {
155- target . properties . push ( sourceProperty )
156- changed = true
157- continue
158- }
159- if ( hasObjectProperty ( target , sourceKey ) ) {
160- continue
161- }
162- target . properties . push ( sourceProperty )
163- changed = true
164- }
165- return changed
166- }
167-
168- function moveProperty (
169- objectExpression : ObjectExpression ,
170- from : string ,
171- to : string ,
172- changes : Set < string > ,
173- scope : OptionObjectScope ,
174- ) {
175- const source = findObjectProperty ( objectExpression , from )
176- if ( ! source ) {
177- return false
178- }
179- const target = findObjectProperty ( objectExpression , to )
180- if ( ! target ) {
181- source . key = keyAsIdentifier ( to )
182- source . computed = false
183- source . shorthand = false
184- changes . add ( `${ scope } .${ from } -> ${ scope } .${ to } ` )
185- return true
186- }
187-
188- if ( t . isObjectExpression ( source . value ) && t . isObjectExpression ( target . value ) ) {
189- const merged = mergeObjectProperties ( target . value , source . value )
190- if ( merged ) {
191- changes . add ( `${ scope } .${ from } merged into ${ scope } .${ to } ` )
192- }
193- }
194- removeObjectProperty ( objectExpression , source )
195- changes . add ( `${ scope } .${ from } removed (preferred ${ scope } .${ to } )` )
196- return true
197- }
198-
199- function migrateExtractOptions ( extract : ObjectExpression , changes : Set < string > , scope : OptionObjectScope ) {
200- let changed = false
201- changed = moveProperty ( extract , 'enabled' , 'write' , changes , scope ) || changed
202- changed = moveProperty ( extract , 'stripUniversalSelector' , 'removeUniversalSelector' , changes , scope ) || changed
203- return changed
204- }
205-
206- function migrateTailwindOptions ( tailwindcss : ObjectExpression , changes : Set < string > , scope : OptionObjectScope ) {
207- let changed = false
208- changed = moveProperty ( tailwindcss , 'package' , 'packageName' , changes , scope ) || changed
209- changed = moveProperty ( tailwindcss , 'legacy' , 'v2' , changes , scope ) || changed
210- changed = moveProperty ( tailwindcss , 'classic' , 'v3' , changes , scope ) || changed
211- changed = moveProperty ( tailwindcss , 'next' , 'v4' , changes , scope ) || changed
212- return changed
213- }
214-
215- function migrateApplyOptions ( apply : ObjectExpression , changes : Set < string > , scope : OptionObjectScope ) {
216- return moveProperty ( apply , 'exportContext' , 'exposeContext' , changes , scope )
217- }
218-
219- function ensureObjectExpressionProperty (
220- objectExpression : ObjectExpression ,
221- name : string ,
222- changes : Set < string > ,
223- scope : OptionObjectScope ,
224- ) {
225- const existing = findObjectProperty ( objectExpression , name )
226- if ( existing ) {
227- return t . isObjectExpression ( existing . value ) ? existing . value : undefined
228- }
229- const value = t . objectExpression ( [ ] )
230- objectExpression . properties . push ( t . objectProperty ( keyAsIdentifier ( name ) , value ) )
231- changes . add ( `${ scope } .${ name } created` )
232- return value
233- }
234-
235- function moveOverwriteToApply ( objectExpression : ObjectExpression , changes : Set < string > , scope : OptionObjectScope ) {
236- const overwrite = findObjectProperty ( objectExpression , 'overwrite' )
237- if ( ! overwrite ) {
238- return false
239- }
240- const apply = ensureObjectExpressionProperty ( objectExpression , 'apply' , changes , scope )
241- if ( ! apply ) {
242- return false
243- }
244- const hasApplyOverwrite = hasObjectProperty ( apply , 'overwrite' )
245- if ( ! hasApplyOverwrite ) {
246- apply . properties . push (
247- t . objectProperty ( keyAsIdentifier ( 'overwrite' ) , overwrite . value ) ,
248- )
249- changes . add ( `${ scope } .overwrite -> ${ scope } .apply.overwrite` )
250- }
251- removeObjectProperty ( objectExpression , overwrite )
252- return true
253- }
254-
255- function hasAnyRootLegacyKeys ( objectExpression : ObjectExpression ) {
256- return ROOT_LEGACY_KEYS . some ( key => hasObjectProperty ( objectExpression , key ) )
257- }
258-
259- function migrateOptionObject ( objectExpression : ObjectExpression , scope : OptionObjectScope , changes : Set < string > ) {
260- let changed = false
261- changed = moveProperty ( objectExpression , 'cwd' , 'projectRoot' , changes , scope ) || changed
262- changed = moveProperty ( objectExpression , 'tailwind' , 'tailwindcss' , changes , scope ) || changed
263- changed = moveProperty ( objectExpression , 'features' , 'apply' , changes , scope ) || changed
264- changed = moveProperty ( objectExpression , 'applyPatches' , 'apply' , changes , scope ) || changed
265- changed = moveProperty ( objectExpression , 'output' , 'extract' , changes , scope ) || changed
266- changed = moveOverwriteToApply ( objectExpression , changes , scope ) || changed
267-
268- const extract = findObjectExpressionProperty ( objectExpression , 'extract' )
269- if ( extract ) {
270- changed = migrateExtractOptions ( extract , changes , scope ) || changed
271- }
272- const tailwindcss = findObjectExpressionProperty ( objectExpression , 'tailwindcss' )
273- if ( tailwindcss ) {
274- changed = migrateTailwindOptions ( tailwindcss , changes , scope ) || changed
275- }
276- const apply = findObjectExpressionProperty ( objectExpression , 'apply' )
277- if ( apply ) {
278- changed = migrateApplyOptions ( apply , changes , scope ) || changed
279- }
280-
281- return changed
282- }
283-
284- function unwrapExpression ( node : t . Node ) : t . Node {
285- let current = node
286- while (
287- t . isTSAsExpression ( current )
288- || t . isTSSatisfiesExpression ( current )
289- || t . isTSTypeAssertion ( current )
290- || t . isParenthesizedExpression ( current )
291- ) {
292- current = current . expression
293- }
294- return current
295- }
296-
297- function resolveObjectExpressionFromExpression ( expression : t . Node ) : ObjectExpression | undefined {
298- const unwrapped = unwrapExpression ( expression )
299- if ( t . isObjectExpression ( unwrapped ) ) {
300- return unwrapped
301- }
302- if ( t . isCallExpression ( unwrapped ) ) {
303- const [ firstArg ] = unwrapped . arguments
304- if ( ! firstArg || ! t . isExpression ( firstArg ) ) {
305- return undefined
306- }
307- const firstArgUnwrapped = unwrapExpression ( firstArg )
308- if ( t . isObjectExpression ( firstArgUnwrapped ) ) {
309- return firstArgUnwrapped
310- }
311- }
312- return undefined
313- }
314-
315- function resolveObjectExpressionFromProgram ( program : t . Program , name : string ) : ObjectExpression | undefined {
316- for ( const statement of program . body ) {
317- if ( ! t . isVariableDeclaration ( statement ) ) {
318- continue
319- }
320- for ( const declaration of statement . declarations ) {
321- if ( ! t . isIdentifier ( declaration . id ) || declaration . id . name !== name || ! declaration . init ) {
322- continue
323- }
324- const objectExpression = resolveObjectExpressionFromExpression ( declaration . init )
325- if ( objectExpression ) {
326- return objectExpression
327- }
328- }
329- }
330- return undefined
331- }
332-
333- function resolveRootConfigObjectExpression ( program : t . Program ) : ObjectExpression | undefined {
334- for ( const statement of program . body ) {
335- if ( ! t . isExportDefaultDeclaration ( statement ) ) {
336- continue
337- }
338- const declaration = statement . declaration
339- if ( t . isIdentifier ( declaration ) ) {
340- return resolveObjectExpressionFromProgram ( program , declaration . name )
341- }
342- const objectExpression = resolveObjectExpressionFromExpression ( declaration )
343- if ( objectExpression ) {
344- return objectExpression
345- }
346- }
347- return undefined
348- }
349-
350- export function migrateConfigSource ( source : string ) : ConfigSourceMigrationResult {
351- const ast = parse ( source , {
352- sourceType : 'module' ,
353- plugins : [ 'typescript' , 'jsx' ] ,
354- } )
355- const root = resolveRootConfigObjectExpression ( ast . program )
356- if ( ! root ) {
357- return {
358- changed : false ,
359- code : source ,
360- changes : [ ] ,
361- }
362- }
363-
364- const changes = new Set < string > ( )
365- let changed = false
366-
367- const registry = findObjectExpressionProperty ( root , 'registry' )
368- if ( registry ) {
369- changed = migrateOptionObject ( registry , 'registry' , changes ) || changed
370- }
371-
372- const patch = findObjectExpressionProperty ( root , 'patch' )
373- if ( patch ) {
374- changed = migrateOptionObject ( patch , 'patch' , changes ) || changed
375- }
376-
377- if ( hasAnyRootLegacyKeys ( root ) ) {
378- changed = migrateOptionObject ( root , 'root' , changes ) || changed
379- }
380-
381- if ( ! changed ) {
382- return {
383- changed : false ,
384- code : source ,
385- changes : [ ] ,
386- }
387- }
388-
389- const generated = generate ( ast , {
390- comments : true ,
391- } ) . code
392- const code = source . endsWith ( '\n' ) ? `${ generated } \n` : generated
393- return {
394- changed : true ,
395- code,
396- changes : [ ...changes ] ,
397- }
398- }
399-
40086export async function migrateConfigFiles ( options : MigrateConfigFilesOptions ) : Promise < ConfigFileMigrationReport > {
40187 const cwd = path . resolve ( options . cwd )
40288 const dryRun = options . dryRun ?? false
0 commit comments