@@ -141,35 +141,14 @@ func UpdateDependabotConfig(dependabotConfig string) (*UpdateDependabotConfigRes
141141 return response , nil
142142 }
143143
144- // Using strings.Builder for efficient string concatenation
145- var finalOutput strings.Builder
146- finalOutput .WriteString (response .FinalOutput )
147-
148144 if updateDependabotConfigRequest .Content == "" {
145+ // Empty content: build from scratch using string concatenation.
149146 if len (updateDependabotConfigRequest .Ecosystems ) == 0 {
150147 return response , nil
151148 }
149+ var finalOutput strings.Builder
152150 finalOutput .WriteString ("version: 2\n updates:" )
153- } else {
154- if ! strings .HasSuffix (response .FinalOutput , "\n " ) {
155- finalOutput .WriteString ("\n " )
156- }
157- indentation , err = getIndentation (string (inputConfigFile ))
158- if err != nil {
159- return nil , fmt .Errorf ("failed to get indentation: %v" , err )
160- }
161- }
162-
163- for _ , Update := range updateDependabotConfigRequest .Ecosystems {
164- updateAlreadyExist := false
165- for _ , update := range cfg .Updates {
166- if update .PackageEcosystem == Update .PackageEcosystem && (update .Directory == Update .Directory || update .Directory == Update .Directory + "/" ) {
167- updateAlreadyExist = true
168- break
169- }
170- }
171-
172- if ! updateAlreadyExist {
151+ for _ , Update := range updateDependabotConfigRequest .Ecosystems {
173152 item := ExtendedUpdate {
174153 Update : dependabot.Update {
175154 PackageEcosystem : Update .PackageEcosystem ,
@@ -184,18 +163,83 @@ func UpdateDependabotConfig(dependabotConfig string) (*UpdateDependabotConfigRes
184163 if err != nil {
185164 return nil , fmt .Errorf ("failed to marshal update items: %v" , err )
186165 }
187-
188166 data , err := addIndentation (string (addedItem ), indentation )
189167 if err != nil {
190168 return nil , fmt .Errorf ("failed to add indentation: %v" , err )
191169 }
192170 finalOutput .WriteString (data )
193171 response .IsChanged = true
194172 }
195- }
173+ response .FinalOutput = finalOutput .String ()
174+ } else {
175+ // Non-empty content: insert new entries at the end of the updates section
176+ // so that sibling top-level keys like registries are preserved in place.
177+ indentation , err = getIndentation (string (inputConfigFile ))
178+ if err != nil {
179+ return nil , fmt .Errorf ("failed to get indentation: %v" , err )
180+ }
181+
182+ var rootNode yaml.Node
183+ if err := yaml .Unmarshal (inputConfigFile , & rootNode ); err != nil {
184+ return nil , fmt .Errorf ("failed to parse yaml for insertion point: %v" , err )
185+ }
186+ if len (rootNode .Content ) == 0 {
187+ return nil , fmt .Errorf ("failed to parse yaml: document is empty" )
188+ }
189+ docNode := rootNode .Content [0 ]
190+ updatesNode := findMappingValue (docNode , "updates" )
191+ if updatesNode == nil || updatesNode .Kind != yaml .SequenceNode {
192+ return nil , fmt .Errorf ("missing or invalid 'updates' section in dependabot config" )
193+ }
196194
197- // Set FinalOutput to the built string
198- response .FinalOutput = finalOutput .String ()
195+ inputLines := strings .Split (response .FinalOutput , "\n " )
196+ updatesLastLine := findLastLine (updatesNode )
197+ lineOffset := 0
198+
199+ for _ , Update := range updateDependabotConfigRequest .Ecosystems {
200+ updateAlreadyExist := false
201+ for _ , update := range cfg .Updates {
202+ if update .PackageEcosystem == Update .PackageEcosystem && (update .Directory == Update .Directory || update .Directory == Update .Directory + "/" ) {
203+ updateAlreadyExist = true
204+ break
205+ }
206+ }
207+
208+ if ! updateAlreadyExist {
209+ item := ExtendedUpdate {
210+ Update : dependabot.Update {
211+ PackageEcosystem : Update .PackageEcosystem ,
212+ Directory : Update .Directory ,
213+ Schedule : dependabot.Schedule {Interval : Update .Interval },
214+ },
215+ Groups : Update .Groups ,
216+ CoolDown : Update .CoolDown ,
217+ }
218+ items := []ExtendedUpdate {item }
219+ addedItem , err := yaml .Marshal (items )
220+ if err != nil {
221+ return nil , fmt .Errorf ("failed to marshal update items: %v" , err )
222+ }
223+ data , err := addIndentation (string (addedItem ), indentation )
224+ if err != nil {
225+ return nil , fmt .Errorf ("failed to add indentation: %v" , err )
226+ }
227+
228+ // Trim trailing newline to avoid double blank lines when content
229+ // follows after the updates section (e.g. registries block).
230+ dataLines := strings .Split (strings .TrimRight (data , "\n " ), "\n " )
231+ insertAt := updatesLastLine + lineOffset
232+ inputLines = insertAfterLine (inputLines , insertAt , dataLines )
233+ lineOffset += len (dataLines )
234+ response .IsChanged = true
235+ }
236+ }
237+
238+ response .FinalOutput = strings .Join (inputLines , "\n " )
239+ if ! strings .HasSuffix (response .FinalOutput , "\n " ) {
240+ response .FinalOutput += "\n "
241+ }
242+ }
199243
200244 return response , nil
201245}
@@ -258,6 +302,16 @@ func replaceScalarOnLine(lines []string, lineIdx int, node *yaml.Node, newVal st
258302 line := lines [lineIdx ]
259303 col := node .Column - 1
260304 end := col + len (node .Value )
305+ // When the original value is quoted, node.Column points to the opening quote
306+ // but node.Value is the unquoted content. Account for the surrounding quotes.
307+ if node .Style == yaml .DoubleQuotedStyle || node .Style == yaml .SingleQuotedStyle {
308+ end += 2 // skip both opening and closing quote
309+ if node .Style == yaml .DoubleQuotedStyle {
310+ newVal = "\" " + newVal + "\" "
311+ } else {
312+ newVal = "'" + newVal + "'"
313+ }
314+ }
261315 if end > len (line ) {
262316 end = len (line )
263317 }
@@ -765,13 +819,10 @@ func updateSubtractiveFields(content string, ecosystems []Ecosystem, cfg Config,
765819 return content , false , nil
766820 }
767821
768- result := strings . Join ( inputLines , " \n " )
769-
770- // Append ecosystems that were not found in existing config (additive).
822+ // Insert ecosystems that were not found in existing config at the end of the
823+ // updates section, so sibling top-level keys like registries stay in place.
824+ updatesLastLine := findLastLine ( updatesNode ) + lineOffset
771825 for _ , eco := range toAdd {
772- if ! strings .HasSuffix (result , "\n " ) {
773- result += "\n "
774- }
775826 item := ExtendedUpdate {
776827 Update : dependabot.Update {
777828 PackageEcosystem : eco .PackageEcosystem ,
@@ -792,9 +843,17 @@ func updateSubtractiveFields(content string, ecosystems []Ecosystem, cfg Config,
792843 if err != nil {
793844 return "" , false , fmt .Errorf ("failed to add indentation: %w" , err )
794845 }
795- result += data
846+
847+ dataLines := strings .Split (strings .TrimRight (data , "\n " ), "\n " )
848+ inputLines = insertAfterLine (inputLines , updatesLastLine , dataLines )
849+ updatesLastLine += len (dataLines )
850+ changed = true
796851 }
797852
853+ result := strings .Join (inputLines , "\n " )
854+ if ! strings .HasSuffix (result , "\n " ) {
855+ result += "\n "
856+ }
798857 return result , true , nil
799858}
800859
0 commit comments