@@ -124,47 +124,87 @@ def _write_java_build_config(project_root: Path, config: CodeflashConfig) -> tup
124124 return _write_gradle_properties (gradle_props_path , non_default )
125125
126126
127+ _MAVEN_KEY_MAP : dict [str , str ] = {
128+ "module-root" : "moduleRoot" ,
129+ "tests-root" : "testsRoot" ,
130+ "git-remote" : "gitRemote" ,
131+ "disable-telemetry" : "disableTelemetry" ,
132+ "ignore-paths" : "ignorePaths" ,
133+ "formatter-cmds" : "formatterCmds" ,
134+ }
135+
136+
127137def _write_maven_properties (pom_path : Path , config : dict [str , Any ]) -> tuple [bool , str ]:
128- """Add codeflash.* properties to pom.xml <properties> section."""
129- import xml .etree .ElementTree as ET
138+ """Add codeflash.* properties to pom.xml <properties> section.
139+
140+ Uses text-based manipulation to preserve comments, formatting, and namespace declarations.
141+ """
142+ import re
130143
131144 try :
132- tree = ET . parse ( str ( pom_path ) )
133- root = tree . getroot ()
134- ns = { "m" : "http://maven.apache.org/POM/4.0.0" }
135-
136- # Find or create <properties>
137- properties = root . find ( "m:properties" , ns ) or root . find ( " properties" )
138- if properties is None :
139- properties = ET . SubElement ( root , "properties" )
140-
141- # Convert kebab-case keys to camelCase for Maven convention
142- key_map = {
143- "module-root" : "moduleRoot " ,
144- "tests-root" : "testsRoot " ,
145- "git-remote" : "gitRemote" ,
146- "disable-telemetry" : "disableTelemetry" ,
147- "ignore-paths" : "ignorePaths" ,
148- "formatter-cmds" : "formatterCmds" ,
149- }
145+ content = pom_path . read_text ( encoding = "utf-8" )
146+
147+ # Remove existing codeflash.* property lines (with surrounding whitespace)
148+ content = re . sub ( r"\n[ \t]*<codeflash\.[^>]*>[^<]*</codeflash\.[^>]*>" , "" , content )
149+
150+ # Detect child indentation from existing properties or fall back to </ properties> indent + 4 spaces
151+ props_close = re . search ( r"([ \t]*)</properties>" , content )
152+ if props_close :
153+ parent_indent = props_close . group ( 1 )
154+ # Try to detect child indent from an existing property element
155+ child_match = re . search (
156+ r"\n([ \t]+)<[a-zA-Z] " ,
157+ content [ content . find ( "<properties>" ) : props_close . start ()] if "<properties>" in content else " " ,
158+ )
159+ child_indent = child_match . group ( 1 ) if child_match else parent_indent + " "
160+ else :
161+ parent_indent = ""
162+ child_indent = " "
150163
164+ # Build new property lines with detected indentation
165+ new_lines = []
151166 for key , value in config .items ():
152- maven_key = f"codeflash.{ key_map .get (key , key )} "
167+ maven_key = f"codeflash.{ _MAVEN_KEY_MAP .get (key , key )} "
153168 if isinstance (value , list ):
154169 value = "," .join (str (v ) for v in value )
155170 elif isinstance (value , bool ):
156171 value = str (value ).lower ()
157172 else :
158173 value = str (value )
159-
160- existing = properties .find (maven_key )
161- if existing is None :
162- elem = ET .SubElement (properties , maven_key )
163- elem .text = value
164- else :
165- existing .text = value
166-
167- tree .write (str (pom_path ), xml_declaration = True , encoding = "UTF-8" )
174+ new_lines .append (f"{ child_indent } <{ maven_key } >{ value } </{ maven_key } >" )
175+
176+ properties_block = "\n " .join (new_lines )
177+
178+ # Insert before </properties>
179+ if props_close :
180+ content = (
181+ content [: props_close .start ()]
182+ + properties_block
183+ + "\n "
184+ + parent_indent
185+ + "</properties>"
186+ + content [props_close .end () :]
187+ )
188+ else :
189+ # No <properties> section — create one before </project>
190+ project_close = re .search (r"([ \t]*)</project>" , content )
191+ if project_close :
192+ indent = project_close .group (1 )
193+ inner = " " + indent
194+ props_section = (
195+ f"{ inner } <properties>\n "
196+ + "\n " .join (f" { line } " for line in new_lines )
197+ + f"\n { inner } </properties>\n "
198+ )
199+ content = (
200+ content [: project_close .start ()]
201+ + props_section
202+ + indent
203+ + "</project>"
204+ + content [project_close .end () :]
205+ )
206+
207+ pom_path .write_text (content , encoding = "utf-8" )
168208 return True , f"Config saved to { pom_path } <properties>"
169209
170210 except Exception as e :
@@ -173,15 +213,6 @@ def _write_maven_properties(pom_path: Path, config: dict[str, Any]) -> tuple[boo
173213
174214def _write_gradle_properties (props_path : Path , config : dict [str , Any ]) -> tuple [bool , str ]:
175215 """Add codeflash.* entries to gradle.properties."""
176- key_map = {
177- "module-root" : "moduleRoot" ,
178- "tests-root" : "testsRoot" ,
179- "git-remote" : "gitRemote" ,
180- "disable-telemetry" : "disableTelemetry" ,
181- "ignore-paths" : "ignorePaths" ,
182- "formatter-cmds" : "formatterCmds" ,
183- }
184-
185216 try :
186217 lines = []
187218 if props_path .exists ():
@@ -195,7 +226,7 @@ def _write_gradle_properties(props_path: Path, config: dict[str, Any]) -> tuple[
195226 lines .append ("" )
196227 lines .append ("# Codeflash configuration — https://docs.codeflash.ai" )
197228 for key , value in config .items ():
198- gradle_key = f"codeflash.{ key_map .get (key , key )} "
229+ gradle_key = f"codeflash.{ _MAVEN_KEY_MAP .get (key , key )} "
199230 if isinstance (value , list ):
200231 value = "," .join (str (v ) for v in value )
201232 elif isinstance (value , bool ):
@@ -306,8 +337,25 @@ def _remove_from_pyproject(project_root: Path) -> tuple[bool, str]:
306337
307338
308339def _remove_java_build_config (project_root : Path ) -> tuple [bool , str ]:
309- """Remove codeflash.* properties from pom.xml or gradle.properties."""
310- # Try gradle.properties first (simpler)
340+ """Remove codeflash.* properties from pom.xml or gradle.properties.
341+
342+ Priority matches _write_java_build_config: pom.xml first, then gradle.properties.
343+ """
344+ # Try pom.xml first (matches write priority) — text-based removal preserves formatting
345+ pom_path = project_root / "pom.xml"
346+ if pom_path .exists ():
347+ try :
348+ import re
349+
350+ content = pom_path .read_text (encoding = "utf-8" )
351+ updated = re .sub (r"\n[ \t]*<codeflash\.[^>]*>[^<]*</codeflash\.[^>]*>" , "" , content )
352+ if updated != content :
353+ pom_path .write_text (updated , encoding = "utf-8" )
354+ return True , "Removed codeflash properties from pom.xml"
355+ except Exception as e :
356+ return False , f"Failed to remove config from pom.xml: { e } "
357+
358+ # Try gradle.properties
311359 gradle_props = project_root / "gradle.properties"
312360 if gradle_props .exists ():
313361 try :
@@ -316,33 +364,13 @@ def _remove_java_build_config(project_root: Path) -> tuple[bool, str]:
316364 line
317365 for line in lines
318366 if not line .strip ().startswith ("codeflash." )
319- and line .strip () != "# Codeflash configuration — https://docs.codeflash.ai"
367+ and line .strip () != "# Codeflash configuration \u2014 https://docs.codeflash.ai"
320368 ]
321369 gradle_props .write_text ("\n " .join (filtered ) + "\n " , encoding = "utf-8" )
322370 return True , "Removed codeflash properties from gradle.properties"
323371 except Exception as e :
324372 return False , f"Failed to remove config from gradle.properties: { e } "
325373
326- # Try pom.xml
327- pom_path = project_root / "pom.xml"
328- if pom_path .exists ():
329- try :
330- import xml .etree .ElementTree as ET
331-
332- tree = ET .parse (str (pom_path ))
333- root = tree .getroot ()
334- ns = {"m" : "http://maven.apache.org/POM/4.0.0" }
335- for properties in [root .find ("m:properties" , ns ), root .find ("properties" )]:
336- if properties is None :
337- continue
338- to_remove = [child for child in properties if child .tag .split ("}" )[- 1 ].startswith ("codeflash." )]
339- for elem in to_remove :
340- properties .remove (elem )
341- tree .write (str (pom_path ), xml_declaration = True , encoding = "UTF-8" )
342- return True , "Removed codeflash properties from pom.xml"
343- except Exception as e :
344- return False , f"Failed to remove config from pom.xml: { e } "
345-
346374 return True , "No Java build config found"
347375
348376
0 commit comments