@@ -26,7 +26,7 @@ import {
26
26
} from '../../../../tailwindcss/src/utils/infer-data-type'
27
27
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
28
28
import { findStaticPlugins , type StaticPluginOptions } from '../../utils/extract-static-plugins'
29
- import { highlight , info , relative } from '../../utils/renderer'
29
+ import { highlight , info , relative , warn } from '../../utils/renderer'
30
30
31
31
const __filename = fileURLToPath ( import . meta. url )
32
32
const __dirname = path . dirname ( __filename )
@@ -49,11 +49,15 @@ export async function migrateJsConfig(
49
49
fs . readFile ( fullConfigPath , 'utf-8' ) ,
50
50
] )
51
51
52
- if ( ! canMigrateConfig ( unresolvedConfig , source ) ) {
52
+ let canMigrateConfigResult = canMigrateConfig ( unresolvedConfig , source )
53
+ if ( ! canMigrateConfigResult . valid ) {
53
54
info (
54
55
`The configuration file at ${ highlight ( relative ( fullConfigPath , base ) ) } could not be automatically migrated to the new CSS configuration format, so your CSS has been updated to load your existing configuration file.` ,
55
56
{ prefix : '↳ ' } ,
56
57
)
58
+ for ( let msg of canMigrateConfigResult . errors ) {
59
+ warn ( msg , { prefix : ' ↳ ' } )
60
+ }
57
61
return null
58
62
}
59
63
@@ -384,22 +388,34 @@ async function migrateContent(
384
388
return sources
385
389
}
386
390
387
- // Applies heuristics to determine if we can attempt to migrate the config
388
- function canMigrateConfig ( unresolvedConfig : Config , source : string ) : boolean {
389
- // The file may not contain non-serializable values
390
- function isSimpleValue ( value : unknown ) : boolean {
391
- if ( typeof value === 'function' ) return false
392
- if ( Array . isArray ( value ) ) return value . every ( isSimpleValue )
393
- if ( typeof value === 'object' && value !== null ) {
394
- return Object . values ( value ) . every ( isSimpleValue )
391
+ const JS_IDENTIFIER_REGEX = / ^ [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * $ /
392
+ function stringifyPath ( path : ( string | number ) [ ] ) : string {
393
+ let result = ''
394
+
395
+ for ( let segment of path ) {
396
+ if ( typeof segment === 'number' ) {
397
+ result += `[${ segment } ]`
398
+ } else if ( ! JS_IDENTIFIER_REGEX . test ( segment ) ) {
399
+ result += `[\`${ segment } \`]`
400
+ } else {
401
+ result += result . length > 0 ? `.${ segment } ` : segment
395
402
}
396
- return [ 'string' , 'number' , 'boolean' , 'undefined' ] . includes ( typeof value )
397
403
}
398
404
399
- // `theme` and `plugins` are handled separately and allowed to be more complex
400
- let { plugins, theme, ...remainder } = unresolvedConfig
401
- if ( ! isSimpleValue ( remainder ) ) {
402
- return false
405
+ return result
406
+ }
407
+
408
+ // Applies heuristics to determine if we can attempt to migrate the config
409
+ function canMigrateConfig (
410
+ unresolvedConfig : Config ,
411
+ source : string ,
412
+ ) : { valid : true } | { valid : false ; errors : string [ ] } {
413
+ let theme = unresolvedConfig . theme
414
+ let errors : string [ ] = [ ]
415
+
416
+ // Migrating presets are not supported
417
+ if ( unresolvedConfig . presets && unresolvedConfig . presets . length > 0 ) {
418
+ errors . push ( 'Cannot migrate config files that use presets' )
403
419
}
404
420
405
421
// The file may only contain known-migratable top-level properties
@@ -413,49 +429,55 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
413
429
'corePlugins' ,
414
430
]
415
431
416
- if ( Object . keys ( unresolvedConfig ) . some ( ( key ) => ! knownProperties . includes ( key ) ) ) {
417
- return false
418
- }
419
-
420
- if ( findStaticPlugins ( source ) === null ) {
421
- return false
422
- }
423
-
424
- if ( unresolvedConfig . presets && unresolvedConfig . presets . length > 0 ) {
425
- return false
432
+ for ( let key of Object . keys ( unresolvedConfig ) ) {
433
+ if ( ! knownProperties . includes ( key ) ) {
434
+ errors . push ( `Cannot migrate unknown top-level key: \`${ key } \`` )
435
+ }
426
436
}
427
437
428
438
// Only migrate the config file if all top-level theme keys are allowed to be
429
439
// migrated
430
440
if ( theme && typeof theme === 'object' ) {
431
- if ( theme . extend && ! onlyAllowedThemeValues ( theme . extend ) ) return false
432
- let { extend : _extend , ...themeCopy } = theme
433
- if ( ! onlyAllowedThemeValues ( themeCopy ) ) return false
441
+ let { extend, ...themeCopy } = theme
442
+ errors . push ( ...onlyAllowedThemeValues ( themeCopy , [ 'theme' ] ) )
443
+
444
+ if ( extend ) {
445
+ errors . push ( ...onlyAllowedThemeValues ( extend , [ 'theme' , 'extend' ] ) )
446
+ }
434
447
}
435
448
436
- return true
449
+ // TODO: findStaticPlugins already logs errors for unsupported plugins, maybe
450
+ // it should return them instead?
451
+ findStaticPlugins ( source )
452
+
453
+ return errors . length <= 0 ? { valid : true } : { valid : false , errors }
437
454
}
438
455
439
456
const ALLOWED_THEME_KEYS = [
440
457
...Object . keys ( defaultTheme ) ,
441
458
// Used by @tailwindcss /container-queries
442
459
'containers' ,
443
460
]
444
- function onlyAllowedThemeValues ( theme : ThemeConfig ) : boolean {
461
+ function onlyAllowedThemeValues ( theme : ThemeConfig , path : ( string | number ) [ ] ) : string [ ] {
462
+ let errors : string [ ] = [ ]
463
+
445
464
for ( let key of Object . keys ( theme ) ) {
446
465
if ( ! ALLOWED_THEME_KEYS . includes ( key ) ) {
447
- return false
466
+ errors . push ( `Cannot migrate theme key: \` ${ stringifyPath ( [ ... path , key ] ) } \`` )
448
467
}
449
468
}
450
469
451
470
if ( 'screens' in theme && typeof theme . screens === 'object' && theme . screens !== null ) {
452
- for ( let screen of Object . values ( theme . screens ) ) {
471
+ for ( let [ name , screen ] of Object . entries ( theme . screens ) ) {
453
472
if ( typeof screen === 'object' && screen !== null && ( 'max' in screen || 'raw' in screen ) ) {
454
- return false
473
+ errors . push (
474
+ `Cannot migrate complex screen definition: \`${ stringifyPath ( [ ...path , 'screens' , name ] ) } \`` ,
475
+ )
455
476
}
456
477
}
457
478
}
458
- return true
479
+
480
+ return errors
459
481
}
460
482
461
483
function keyframesToCss ( keyframes : Record < string , unknown > ) : string {
0 commit comments