SaveConfigPreserveComments writes the config back to YAML while preserving existing comments and key ordering by loading the original file into a yaml.Node tree and updating values in-place.
(configFile string, cfg *Config)
| 1184 | // SaveConfigPreserveComments writes the config back to YAML while preserving existing comments |
| 1185 | // and key ordering by loading the original file into a yaml.Node tree and updating values in-place. |
| 1186 | func SaveConfigPreserveComments(configFile string, cfg *Config) error { |
| 1187 | persistCfg := cfg |
| 1188 | // Load original YAML as a node tree to preserve comments and ordering. |
| 1189 | data, err := os.ReadFile(configFile) |
| 1190 | if err != nil { |
| 1191 | return err |
| 1192 | } |
| 1193 | |
| 1194 | var original yaml.Node |
| 1195 | if err = yaml.Unmarshal(data, &original); err != nil { |
| 1196 | return err |
| 1197 | } |
| 1198 | if original.Kind != yaml.DocumentNode || len(original.Content) == 0 { |
| 1199 | return fmt.Errorf("invalid yaml document structure") |
| 1200 | } |
| 1201 | if original.Content[0] == nil || original.Content[0].Kind != yaml.MappingNode { |
| 1202 | return fmt.Errorf("expected root mapping node") |
| 1203 | } |
| 1204 | |
| 1205 | // Marshal the current cfg to YAML, then unmarshal to a yaml.Node we can merge from. |
| 1206 | rendered, err := yaml.Marshal(persistCfg) |
| 1207 | if err != nil { |
| 1208 | return err |
| 1209 | } |
| 1210 | var generated yaml.Node |
| 1211 | if err = yaml.Unmarshal(rendered, &generated); err != nil { |
| 1212 | return err |
| 1213 | } |
| 1214 | if generated.Kind != yaml.DocumentNode || len(generated.Content) == 0 || generated.Content[0] == nil { |
| 1215 | return fmt.Errorf("invalid generated yaml structure") |
| 1216 | } |
| 1217 | if generated.Content[0].Kind != yaml.MappingNode { |
| 1218 | return fmt.Errorf("expected generated root mapping node") |
| 1219 | } |
| 1220 | |
| 1221 | // Remove deprecated sections before merging back the sanitized config. |
| 1222 | removeLegacyAuthBlock(original.Content[0]) |
| 1223 | removeLegacyOpenAICompatAPIKeys(original.Content[0]) |
| 1224 | removeRemovedIntegrationKeys(original.Content[0]) |
| 1225 | removeLegacyGenerativeLanguageKeys(original.Content[0]) |
| 1226 | |
| 1227 | pruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], "oauth-excluded-models") |
| 1228 | pruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], "oauth-model-alias") |
| 1229 | pruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], "plugins", "configs") |
| 1230 | |
| 1231 | // Merge generated into original in-place, preserving comments/order of existing nodes. |
| 1232 | mergeMappingPreserve(original.Content[0], generated.Content[0]) |
| 1233 | normalizeCollectionNodeStyles(original.Content[0]) |
| 1234 | |
| 1235 | // Write back. |
| 1236 | f, err := os.Create(configFile) |
| 1237 | if err != nil { |
| 1238 | return err |
| 1239 | } |
| 1240 | defer func() { _ = f.Close() }() |
| 1241 | var buf bytes.Buffer |
| 1242 | enc := yaml.NewEncoder(&buf) |
| 1243 | enc.SetIndent(2) |