@@ -88,6 +88,7 @@ public OptionsConfigurationBuilder UpgradeConfiguration(OptionsConfigurationBuil
8888 optionsBuilder . AddOption ( ParseV1FieldMaps ( configuration ) ) ;
8989 optionsBuilder . AddOption ( ParseSectionCollectionWithTypePropertyNameToList ( configuration , "Processors" , "$type" ) ) ;
9090 optionsBuilder . AddOption ( ParseSectionCollectionWithTypePropertyNameToList ( configuration , "CommonEnrichersConfig" , "$type" ) ) ;
91+ optionsBuilder . AddOption ( ParseSectionCollectionWithTypePropertyNameToList ( configuration , "CommonTools" , "$type" ) ) ;
9192 if ( ! IsSectionNullOrEmpty ( configuration . GetSection ( "Source" ) ) || ! IsSectionNullOrEmpty ( configuration . GetSection ( "Target" ) ) )
9293 {
9394 optionsBuilder . AddOption ( ParseSectionWithTypePropertyNameToOptions ( configuration , "Source" , "$type" ) , "Source" ) ;
@@ -154,47 +155,120 @@ private List<IOptions> ParseSectionCollectionWithTypePropertyNameToList(IConfigu
154155 var newOptionTypeString = ParseOptionsType ( optionTypeString ) ;
155156 _logger . LogDebug ( "Upgrading {group} item {old} to {new}" , path , optionTypeString , newOptionTypeString ) ;
156157 var option = GetOptionWithDefaults ( configuration , newOptionTypeString ) ;
157- childSection . Bind ( option ) ;
158- switch ( optionTypeString )
158+ if ( option != null )
159159 {
160- case "TfsNodeStructureOptions" :
161- MapTfsNodeStructureOptions ( childSection , option ) ;
162- _logger . LogWarning ( "Empty type string found in {path}" , path ) ;
163- break ;
164- default :
165- break ;
160+ childSection . Bind ( option ) ;
161+ switch ( optionTypeString )
162+ {
163+ case "TfsNodeStructureOptions" :
164+ MapTfsNodeStructureOptions ( childSection , option ) ;
165+ break ;
166+ default :
167+ break ;
168+ }
169+ options . Add ( option ) ;
170+ }
171+ else
172+ {
173+ _logger . LogWarning ( "Could not create option for type {newOptionTypeString} (original: {optionTypeString})" , newOptionTypeString , optionTypeString ) ;
166174 }
167-
168-
169- options . Add ( option ) ;
170175 }
171176
172177 return options ;
173178 }
174179
175180 private void MapTfsNodeStructureOptions ( IConfigurationSection section , dynamic option )
176181 {
177- // Map AreaMaps from the old structure to the new Areas.Mappings
182+ // Handle conversion from old dictionary format to new array format for Areas.Mappings
183+ var areasMappingsSection = section . GetSection ( "Areas:Mappings" ) ;
184+ if ( areasMappingsSection . Exists ( ) && areasMappingsSection . GetChildren ( ) . Any ( ) )
185+ {
186+ // Check if this is a dictionary format (children have Key/Value pairs rather than indexed)
187+ var firstChild = areasMappingsSection . GetChildren ( ) . FirstOrDefault ( ) ;
188+ if ( firstChild != null && ! int . TryParse ( firstChild . Key , out _ ) )
189+ {
190+ // This is dictionary format, convert to NodeMapping objects
191+ option . Areas . Mappings . Clear ( ) ;
192+ foreach ( var mapping in areasMappingsSection . GetChildren ( ) )
193+ {
194+ // Create NodeMapping using reflection or dynamic approach
195+ dynamic nodeMapping = CreateNodeMapping ( mapping . Key , mapping . Value ) ;
196+ option . Areas . Mappings . Add ( nodeMapping ) ;
197+ }
198+ _logger . LogDebug ( "Converted Areas.Mappings from dictionary format to array format for TfsNodeStructureTool" ) ;
199+ }
200+ }
201+
202+ // Handle conversion from old dictionary format to new array format for Iterations.Mappings
203+ var iterationsMappingsSection = section . GetSection ( "Iterations:Mappings" ) ;
204+ if ( iterationsMappingsSection . Exists ( ) && iterationsMappingsSection . GetChildren ( ) . Any ( ) )
205+ {
206+ // Check if this is a dictionary format (children have Key/Value pairs rather than indexed)
207+ var firstChild = iterationsMappingsSection . GetChildren ( ) . FirstOrDefault ( ) ;
208+ if ( firstChild != null && ! int . TryParse ( firstChild . Key , out _ ) )
209+ {
210+ // This is dictionary format, convert to NodeMapping objects
211+ option . Iterations . Mappings . Clear ( ) ;
212+ foreach ( var mapping in iterationsMappingsSection . GetChildren ( ) )
213+ {
214+ // Create NodeMapping using reflection or dynamic approach
215+ dynamic nodeMapping = CreateNodeMapping ( mapping . Key , mapping . Value ) ;
216+ option . Iterations . Mappings . Add ( nodeMapping ) ;
217+ }
218+ _logger . LogDebug ( "Converted Iterations.Mappings from dictionary format to array format for TfsNodeStructureTool" ) ;
219+ }
220+ }
221+
222+ // Legacy support: Map AreaMaps from the old structure to the new Areas.Mappings (if present)
178223 var areaMaps = section . GetSection ( "AreaMaps" ) . GetChildren ( ) ;
179224 foreach ( var areaMap in areaMaps )
180225 {
181226 var key = areaMap . Key ;
182227 var value = areaMap . Value ;
183- option . Areas . Mappings . Add ( key , value ) ;
228+ dynamic nodeMapping = CreateNodeMapping ( key , value ) ;
229+ option . Areas . Mappings . Add ( nodeMapping ) ;
184230 }
185231
186- // Map IterationMaps from the old structure to the new Iterations.Mappings
232+ // Legacy support: Map IterationMaps from the old structure to the new Iterations.Mappings (if present)
187233 var iterationMaps = section . GetSection ( "IterationMaps" ) . GetChildren ( ) ;
188234 foreach ( var iterationMap in iterationMaps )
189235 {
190236 var key = iterationMap . Key ;
191237 var value = iterationMap . Value ;
192- option . Iterations . Mappings . Add ( key , value ) ;
238+ dynamic nodeMapping = CreateNodeMapping ( key , value ) ;
239+ option . Iterations . Mappings . Add ( nodeMapping ) ;
193240 }
194- // Now map the intermediate structure back into the original `option` object
241+
195242 _logger . LogDebug ( "Mapped TfsNodeStructureOptions to TfsNodeStructureTool structure and updated the options object." ) ;
196243 }
197244
245+ private object CreateNodeMapping ( string match , string replacement )
246+ {
247+ // Try to find the NodeMapping type from loaded assemblies
248+ var nodeMappingType = AppDomain . CurrentDomain . GetAssemblies ( )
249+ . SelectMany ( a => a . GetTypes ( ) )
250+ . FirstOrDefault ( t => t . Name == "NodeMapping" && t . Namespace == "MigrationTools.Tools" ) ;
251+
252+ if ( nodeMappingType != null )
253+ {
254+ var nodeMapping = Activator . CreateInstance ( nodeMappingType ) ;
255+ var matchProperty = nodeMappingType . GetProperty ( "Match" ) ;
256+ var replacementProperty = nodeMappingType . GetProperty ( "Replacement" ) ;
257+
258+ if ( matchProperty != null && replacementProperty != null )
259+ {
260+ matchProperty . SetValue ( nodeMapping , match ) ;
261+ replacementProperty . SetValue ( nodeMapping , replacement ) ;
262+ }
263+
264+ return nodeMapping ;
265+ }
266+
267+ // Fallback: create a simple object with the properties
268+ _logger . LogWarning ( "Could not find NodeMapping type, creating fallback object" ) ;
269+ return new { Match = match , Replacement = replacement } ;
270+ }
271+
198272
199273 private List < IOptions > ParseV1FieldMaps ( IConfiguration configuration )
200274 {
@@ -247,25 +321,47 @@ private IOptions ParseV1TfsChangeSetMappingToolOptions(IConfiguration configurat
247321 {
248322 _logger . LogInformation ( "Upgrading {old} to {new}" , "ChangeSetMappingFile" , "TfsChangeSetMappingTool" ) ;
249323 var changeSetMappingOptions = configuration . GetValue < string > ( "ChangeSetMappingFile" ) ;
324+
325+ // Skip if no ChangeSetMappingFile is configured
326+ if ( string . IsNullOrEmpty ( changeSetMappingOptions ) )
327+ {
328+ _logger . LogDebug ( "No ChangeSetMappingFile found, skipping TfsChangeSetMappingTool creation" ) ;
329+ return null ;
330+ }
331+
250332 var properties = new Dictionary < string , object >
251333 {
252334 { "ChangeSetMappingFile" , changeSetMappingOptions }
253335 } ;
254336 var option = ( IOptions ) OptionsBinder . BindToOptions ( "TfsChangeSetMappingToolOptions" , properties , classNameChangeLog ) ;
255- option . Enabled = true ;
337+ if ( option != null )
338+ {
339+ option . Enabled = true ;
340+ }
256341 return option ;
257342 }
258343
259344 private IOptions ParseV1TfsGitRepoMappingOptions ( IConfiguration configuration )
260345 {
261346 _logger . LogInformation ( "Upgrading {old} to {new}" , "GitRepoMapping" , "TfsGitRepoMappingTool" ) ;
262347 var data = configuration . GetValue < Dictionary < string , string > > ( "GitRepoMapping" ) ;
348+
349+ // Skip if no GitRepoMapping is configured
350+ if ( data == null || data . Count == 0 )
351+ {
352+ _logger . LogDebug ( "No GitRepoMapping found, skipping TfsGitRepoMappingTool creation" ) ;
353+ return null ;
354+ }
355+
263356 var properties = new Dictionary < string , object >
264357 {
265358 { "Mappings" , data }
266359 } ;
267360 var option = ( IOptions ) OptionsBinder . BindToOptions ( "TfsGitRepositoryToolOptions" , properties , classNameChangeLog ) ;
268- option . Enabled = true ;
361+ if ( option != null )
362+ {
363+ option . Enabled = true ;
364+ }
269365 return option ;
270366 }
271367
0 commit comments