From 411f24ac8804dc96442172a0ed038b1ab9a7c2f6 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:43:06 -0800 Subject: [PATCH 1/5] Parse string into list if necessary --- cleanrepo/Program.cs | 45 +++++++++++++++++++++++++++++++++++++++++++- cleanrepo/action.yml | 7 ++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/cleanrepo/Program.cs b/cleanrepo/Program.cs index 9e5e40f0..02956005 100644 --- a/cleanrepo/Program.cs +++ b/cleanrepo/Program.cs @@ -49,9 +49,52 @@ static async Task Main(string[] args) builder.Configuration.GetSection(nameof(Options)) .Bind(options); + options.ExcludeOrphanedIncludeFiles = ParseStringListOption( + builder.Configuration, + nameof(Options.ExcludeOrphanedIncludeFiles), + options.ExcludeOrphanedIncludeFiles); + await RunOptions(options); } + private static List? ParseStringListOption( + ConfigurationManager configuration, + string optionName, + List? currentValue) + { + if (currentValue is { Count: > 0 }) + return currentValue; + + string key = $"{nameof(Options)}:{optionName}"; + string? rawValue = configuration[key]; + + if (string.IsNullOrWhiteSpace(rawValue)) + return currentValue; + + rawValue = rawValue.Trim(); + + // Reusable workflow inputs can pass this as a JSON array string. + if (rawValue.StartsWith('[') && rawValue.EndsWith(']')) + { + try + { + return JsonSerializer.Deserialize>(rawValue) ?? currentValue; + } + catch (JsonException) + { + // Fall through and try simple delimited parsing. + } + } + + List values = [.. rawValue + .Split([',', ';', '\n', '\r'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(value => value.Trim('"')) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .Distinct(StringComparer.InvariantCultureIgnoreCase)]; + + return values.Count > 0 ? values : currentValue; + } + static async Task RunOptions(Options options) { if (string.IsNullOrEmpty(options.Function)) @@ -237,7 +280,7 @@ static async Task RunOptions(Options options) $"in the '{options.TargetDirectory}' directory...\n"); // Extract hash keys from the dictionary. - List mediaFilesList = docFxRepo._imageRefs.Keys.ToList(); + List mediaFilesList = [.. docFxRepo._imageRefs.Keys]; if (mediaFilesList.Count == 0) { diff --git a/cleanrepo/action.yml b/cleanrepo/action.yml index ee2826e8..eede8492 100644 --- a/cleanrepo/action.yml +++ b/cleanrepo/action.yml @@ -7,7 +7,7 @@ branding: color: 'purple' inputs: function: - description: 'The function you want to run: FindOrphanedArticles, FindOrphanedImages, FindOrphanedIncludes, FindOrphanedSnippets, CatalogImages, CatalogImagesWithText, FilterImagesForText, ReplaceRedirectTargets, ReplaceWithRelativeLinks, or RemoveRedirectHops.' + description: 'The function you want to run: FindOrphanedArticles, FindOrphanedImages, FindOrphanedIncludes, FindOrphanedSnippets, ReplaceRedirectTargets, ReplaceWithRelativeLinks, or RemoveRedirectHops.' required: true docfx_directory: description: 'The directory that contains the docfx.json file for the docset.' @@ -18,6 +18,9 @@ inputs: url_base_path: description: 'The URL base path for your docset, for example, /dotnet or /windows/uwp.' required: true + exclude_include_files: + description: 'A list of file names or paths that should never be considered orphaned.' + default: "[]" runs: using: docker image: ../cleanrepo.Dockerfile @@ -30,3 +33,5 @@ runs: - ${{ inputs.target_directory }} - '/Options:UrlBasePath' - ${{ inputs.url_base_path }} + - '/Options:ExcludeOrphanedIncludeFiles' + - ${{ fromJSON(inputs.exclude_include_files) }} From 2a045184dfd2256ac5c5a026683a2519ff4d892b Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:44:25 -0800 Subject: [PATCH 2/5] small udate --- cleanrepo/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cleanrepo/action.yml b/cleanrepo/action.yml index eede8492..6888a3a3 100644 --- a/cleanrepo/action.yml +++ b/cleanrepo/action.yml @@ -20,7 +20,7 @@ inputs: required: true exclude_include_files: description: 'A list of file names or paths that should never be considered orphaned.' - default: "[]" + default: '[]' runs: using: docker image: ../cleanrepo.Dockerfile @@ -34,4 +34,4 @@ runs: - '/Options:UrlBasePath' - ${{ inputs.url_base_path }} - '/Options:ExcludeOrphanedIncludeFiles' - - ${{ fromJSON(inputs.exclude_include_files) }} + - ${{ inputs.exclude_include_files }} From 72650a0bcee3c3271b8a2b311cae67c2b19cd0b0 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:49:57 -0800 Subject: [PATCH 3/5] Improve JSON deserialization error handling Enhance error handling for JSON deserialization. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- cleanrepo/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cleanrepo/Program.cs b/cleanrepo/Program.cs index 02956005..97aa9c02 100644 --- a/cleanrepo/Program.cs +++ b/cleanrepo/Program.cs @@ -80,9 +80,9 @@ static async Task Main(string[] args) { return JsonSerializer.Deserialize>(rawValue) ?? currentValue; } - catch (JsonException) + catch (JsonException ex) { - // Fall through and try simple delimited parsing. + throw new ArgumentException($"Option '{optionName}' must be a valid JSON array of strings.", nameof(optionName), ex); } } From e962a0e2ced9526b69f8bf4c7cc15e03fbf8b725 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:53:19 -0800 Subject: [PATCH 4/5] respond to feedback --- cleanrepo/Program.cs | 46 +++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/cleanrepo/Program.cs b/cleanrepo/Program.cs index 02956005..a827a01e 100644 --- a/cleanrepo/Program.cs +++ b/cleanrepo/Program.cs @@ -58,27 +58,53 @@ static async Task Main(string[] args) } private static List? ParseStringListOption( - ConfigurationManager configuration, + IConfigurationRoot configuration, string optionName, List? currentValue) { - if (currentValue is { Count: > 0 }) - return currentValue; - string key = $"{nameof(Options)}:{optionName}"; - string? rawValue = configuration[key]; + foreach (IConfigurationProvider provider in configuration.Providers.Reverse()) + { + if (provider.TryGet(key, out string? rawValue)) + { + return ParseStringListValue(rawValue); + } + + List children = [.. provider.GetChildKeys([], key)]; + if (children.Count == 0) + continue; + + List values = [.. children + .OrderBy(child => int.TryParse(child, out int index) ? index : int.MaxValue) + .ThenBy(child => child, StringComparer.InvariantCultureIgnoreCase) + .Select(child => + { + provider.TryGet($"{key}:{child}", out string? value); + return value?.Trim(); + }) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .Select(value => value!.Trim('"')) + .Distinct(StringComparer.InvariantCultureIgnoreCase)]; + + return values; + } + + return currentValue; + } + + private static List ParseStringListValue(string? rawValue) + { if (string.IsNullOrWhiteSpace(rawValue)) - return currentValue; + return []; rawValue = rawValue.Trim(); - // Reusable workflow inputs can pass this as a JSON array string. if (rawValue.StartsWith('[') && rawValue.EndsWith(']')) { try { - return JsonSerializer.Deserialize>(rawValue) ?? currentValue; + return JsonSerializer.Deserialize>(rawValue) ?? []; } catch (JsonException) { @@ -86,13 +112,11 @@ static async Task Main(string[] args) } } - List values = [.. rawValue + return [.. rawValue .Split([',', ';', '\n', '\r'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Select(value => value.Trim('"')) .Where(value => !string.IsNullOrWhiteSpace(value)) .Distinct(StringComparer.InvariantCultureIgnoreCase)]; - - return values.Count > 0 ? values : currentValue; } static async Task RunOptions(Options options) From 11344d31ea1274ffcbfd0de02d5132665f72466d Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 4 Nov 2026 13:59:32 -0800 Subject: [PATCH 5/5] Fix compile error --- cleanrepo/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cleanrepo/Program.cs b/cleanrepo/Program.cs index 9e86022e..c14e6633 100644 --- a/cleanrepo/Program.cs +++ b/cleanrepo/Program.cs @@ -108,7 +108,7 @@ private static List ParseStringListValue(string? rawValue) } catch (JsonException ex) { - throw new ArgumentException($"Option '{optionName}' must be a valid JSON array of strings.", nameof(optionName), ex); + throw new ArgumentException($"Option must be a valid JSON array of strings.", ex); } }