@@ -27,17 +27,20 @@ const THEME_TEMPLATE: ThemeFile = {
2727 author : '' ,
2828 type : 'light' ,
2929 colors : {
30- 'color-primary' : 'hsl(277, 22%, 57%)' ,
31- 'color-bg' : 'hsl(35, 67%, 96%)' ,
32- 'color-fg' : 'hsl(245, 18%, 40%)' ,
33- 'color-text' : 'hsl(245, 18%, 40%)' ,
34- 'color-text-muted' : 'hsl(270, 10%, 53%)' ,
35- 'color-border' : 'hsl(30, 24%, 88%)' ,
36- 'color-button' : 'hsl(34, 52%, 91%)' ,
37- 'color-button-hover' : 'hsl(34, 42%, 88%)' ,
38- 'color-list-selection' : 'hsl(34, 38%, 89%)' ,
39- 'color-list-selection-fg' : 'hsl(245, 18%, 40%)' ,
40- 'color-scrollbar' : 'hsla(270, 14%, 73%, 0.5)' ,
30+ 'primary' : 'hsl(277, 22%, 57%)' ,
31+ 'primary-foreground' : 'hsl(0, 0%, 100%)' ,
32+ 'background' : 'hsl(35, 67%, 96%)' ,
33+ 'foreground' : 'hsl(245, 18%, 40%)' ,
34+ 'accent' : 'hsl(34, 38%, 89%)' ,
35+ 'accent-hover' : 'hsl(34, 42%, 92%)' ,
36+ 'accent-foreground' : 'hsl(245, 18%, 40%)' ,
37+ 'muted' : 'hsl(34, 52%, 91%)' ,
38+ 'muted-foreground' : 'hsl(270, 10%, 53%)' ,
39+ 'card' : 'hsl(35, 50%, 94%)' ,
40+ 'popover' : 'hsl(35, 67%, 96%)' ,
41+ 'popover-foreground' : 'hsl(245, 18%, 40%)' ,
42+ 'border' : 'hsl(30, 24%, 88%)' ,
43+ 'scrollbar' : 'hsla(270, 14%, 73%, 0.5)' ,
4144 } ,
4245 editorColors : {
4346 'editor-keyword' : 'hsl(277, 22%, 57%)' ,
@@ -55,6 +58,21 @@ const THEME_TEMPLATE: ThemeFile = {
5558 } ,
5659}
5760
61+ const TOKEN_MIGRATION_MAP : Record < string , string > = {
62+ 'color-primary' : 'primary' ,
63+ 'color-bg' : 'background' ,
64+ 'color-fg' : 'foreground' ,
65+ 'color-text' : 'foreground' ,
66+ 'color-text-muted' : 'muted-foreground' ,
67+ 'color-border' : 'border' ,
68+ 'color-button' : 'muted' ,
69+ 'color-list-selection' : 'accent' ,
70+ 'color-list-selection-fg' : 'accent-foreground' ,
71+ 'color-scrollbar' : 'scrollbar' ,
72+ }
73+
74+ const DROPPED_TOKENS = new Set ( [ 'color-button-hover' ] )
75+
5876let themeWatcher : FSWatcher | null = null
5977let themeWatcherTimer : NodeJS . Timeout | null = null
6078let watchedThemesDir : string | null = null
@@ -224,15 +242,68 @@ function resolveThemeFilePath(id: string): string | null {
224242 return filePath
225243}
226244
245+ function migrateThemeColors ( colors : Record < string , string > ) : {
246+ migrated : Record < string , string >
247+ changed : boolean
248+ } {
249+ const result : Record < string , string > = { }
250+ let changed = false
251+
252+ for ( const [ key , value ] of Object . entries ( colors ) ) {
253+ if ( DROPPED_TOKENS . has ( key ) ) {
254+ changed = true
255+ continue
256+ }
257+
258+ const newKey = TOKEN_MIGRATION_MAP [ key ]
259+
260+ if ( newKey ) {
261+ result [ newKey ] = value
262+ changed = true
263+ }
264+ else {
265+ result [ key ] = value
266+ }
267+ }
268+
269+ return { migrated : result , changed }
270+ }
271+
227272function readThemeFromFile (
228273 filePath : string ,
229274 fileName : string ,
230275) : ThemeFile | null {
231276 try {
232277 const content = readFileSync ( filePath , 'utf8' )
233278 const parsed = JSON . parse ( content ) as unknown
279+ const theme = parseThemeFile ( parsed , fileName )
280+
281+ if ( ! theme ) {
282+ return null
283+ }
284+
285+ if ( theme . colors ) {
286+ const { migrated, changed } = migrateThemeColors ( theme . colors )
287+
288+ if ( changed ) {
289+ theme . colors = migrated
290+
291+ try {
292+ const raw = parsed as Record < string , unknown >
293+ raw . colors = migrated
294+ writeFileSync ( filePath , `${ JSON . stringify ( raw , null , 2 ) } \n` , 'utf8' )
295+ console . warn ( `[theme] Migrated ${ fileName } to new token format` )
296+ }
297+ catch ( writeError ) {
298+ console . warn (
299+ `[theme] Failed to write migrated theme ${ fileName } ` ,
300+ writeError ,
301+ )
302+ }
303+ }
304+ }
234305
235- return parseThemeFile ( parsed , fileName )
306+ return theme
236307 }
237308 catch ( error ) {
238309 reportThemeIssue ( fileName , 'Failed to read or parse JSON' , error )
0 commit comments