@@ -50,6 +50,70 @@ function mergeWithUndefined<T extends object>(target: T, source: object): T {
5050 return result ;
5151}
5252
53+ /**
54+ * Remove duplicate keys within the same TOML section.
55+ * Keeps the last occurrence of each key per section.
56+ */
57+ function deduplicateTomlKeys ( toml : string ) : string {
58+ const lines = toml . split ( '\n' ) ;
59+ const result : string [ ] = [ ] ;
60+ const seenKeys = new Set < string > ( ) ;
61+ let currentSection = '' ;
62+
63+ // First pass: track last occurrence of each key per section
64+ const lastOccurrence = new Map < string , number > ( ) ;
65+ for ( let i = 0 ; i < lines . length ; i ++ ) {
66+ const line = lines [ i ] . trim ( ) ;
67+ if ( line . startsWith ( '[' ) ) {
68+ currentSection = line ;
69+ seenKeys . clear ( ) ;
70+ } else if ( line && ! line . startsWith ( '#' ) ) {
71+ const eqIndex = line . indexOf ( '=' ) ;
72+ if ( eqIndex > 0 ) {
73+ const key = `${ currentSection } ::${ line . substring ( 0 , eqIndex ) . trim ( ) } ` ;
74+ lastOccurrence . set ( key , i ) ;
75+ }
76+ }
77+ }
78+
79+ // Second pass: only keep the last occurrence of duplicate keys
80+ currentSection = '' ;
81+ const keyLineIndices = new Map < string , number [ ] > ( ) ;
82+ for ( let i = 0 ; i < lines . length ; i ++ ) {
83+ const line = lines [ i ] . trim ( ) ;
84+ if ( line . startsWith ( '[' ) ) {
85+ currentSection = line ;
86+ } else if ( line && ! line . startsWith ( '#' ) ) {
87+ const eqIndex = line . indexOf ( '=' ) ;
88+ if ( eqIndex > 0 ) {
89+ const key = `${ currentSection } ::${ line . substring ( 0 , eqIndex ) . trim ( ) } ` ;
90+ if ( ! keyLineIndices . has ( key ) ) {
91+ keyLineIndices . set ( key , [ ] ) ;
92+ }
93+ keyLineIndices . get ( key ) ! . push ( i ) ;
94+ }
95+ }
96+ }
97+
98+ const skipLines = new Set < number > ( ) ;
99+ for ( const [ , indices ] of keyLineIndices ) {
100+ if ( indices . length > 1 ) {
101+ // Skip all but the last occurrence
102+ for ( let j = 0 ; j < indices . length - 1 ; j ++ ) {
103+ skipLines . add ( indices [ j ] ) ;
104+ }
105+ }
106+ }
107+
108+ for ( let i = 0 ; i < lines . length ; i ++ ) {
109+ if ( ! skipLines . has ( i ) ) {
110+ result . push ( lines [ i ] ) ;
111+ }
112+ }
113+
114+ return result . join ( '\n' ) ;
115+ }
116+
53117// Theme configuration types based on theme.schema.json
54118type ThemeLogoConfig = {
55119 src ?: string ;
@@ -751,7 +815,11 @@ export async function modifyConfigToml(
751815 if ( ! res . ok ) throw new Error ( `HTTP ${ res . status } ` ) ;
752816 return res . text ( ) ;
753817 } ) ;
754- config = TOML . parse ( configToml ) ;
818+ // Pre-process TOML to remove duplicate keys before parsing.
819+ // Some server configurations may have duplicate keys (e.g., debug = true
820+ // appearing twice under [general]) which strict TOML parsers reject.
821+ const deduplicatedToml = deduplicateTomlKeys ( configToml ) ;
822+ config = TOML . parse ( deduplicatedToml ) ;
755823 break ; // Success, exit retry loop
756824 } catch ( error ) {
757825 lastError = error ;
0 commit comments