From aadfac767e7d9348ddb5f7af7967ea2c989072ce Mon Sep 17 00:00:00 2001 From: "Andy De George (from Dev Box)" Date: Wed, 25 Feb 2026 15:15:11 -0800 Subject: [PATCH 1/4] Rework the CLI options --- snippets5000/Snippets5000/Program.cs | 121 ++++++++++++++------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/snippets5000/Snippets5000/Program.cs b/snippets5000/Snippets5000/Program.cs index 327ed698..310cc39c 100644 --- a/snippets5000/Snippets5000/Program.cs +++ b/snippets5000/Snippets5000/Program.cs @@ -1,9 +1,7 @@ ๏ปฟusing System.CommandLine; -using System.CommandLine.Parsing; using System.Diagnostics; using System.Text.Json; using System.Text.RegularExpressions; -using DotNet.DocsTools.Utility; using DotNetDocs.Tools.Utility; using static Snippets5000.SnippetsConfigFile; using Log = DotNet.DocsTools.Utility.EchoLogging; @@ -43,7 +41,67 @@ class Program /// 0 on success. Otherwise, a non-zero error code. static async Task Main(string[] args) { - var (sourcepath, pullrequest, owner, repo, dryrunTestId, dryrunTestDataFile) = ParseArguments(args); + Option sourcePathOption = new("--sourcepath") + { + Description = "The directory containing the local source tree.", + Required = true + }; + Option pullrequestOption = new("--pullrequest") + { + Description = "If available, the number of the pull request being built." + }; + Option ownerOption = new("--owner") + { + Description = "If available, the owner organization of the repository." + }; + Option repoOption = new("--repo") + { + Description = "If available, the name of the repository." + }; + Option dryrunTestIdOption = new("--dryrun-test-id") + { + Description = "The test id from data.json to simulate a pull request." + }; + Option dryrunTestDataFileOption = new("--dryrun-test-data-file") + { + Description = "The json file defining all the tests that can be referenced by `dryrunTestId`. Usually data.json." + }; + + RootCommand rootCommand = new("Snippets5000 CI build application.") + { + sourcePathOption, + pullrequestOption, + ownerOption, + repoOption, + dryrunTestIdOption, + dryrunTestDataFileOption + }; + + rootCommand.SetAction(async (parseResult, cancellationToken) => + { + var sourcepath = parseResult.GetValue(sourcePathOption)!; + var pullrequest = parseResult.GetValue(pullrequestOption); + var owner = parseResult.GetValue(ownerOption); + var repo = parseResult.GetValue(repoOption); + var dryrunTestId = parseResult.GetValue(dryrunTestIdOption); + var dryrunTestDataFile = parseResult.GetValue(dryrunTestDataFileOption); + + Console.WriteLine($"Processing source path: {sourcepath}"); + + return await RunAsync(sourcepath, pullrequest, owner, repo, dryrunTestId, dryrunTestDataFile); + }); + + return await rootCommand.Parse(args).InvokeAsync(); + } + + private static async Task RunAsync( + string sourcepath, + int? pullrequest, + string? owner, + string? repo, + string? dryrunTestId, + string? dryrunTestDataFile) + { int exitCode = EXITCODE_GOOD; string appStartupFolder = Directory.GetCurrentDirectory(); @@ -458,61 +516,4 @@ private static void ProcessFailedProjects(string repo, IEnumerable sourcePathOption = new("--sourcepath") - { - Description = "The directory containing the local source tree." - }; - Option pullrequestOption = new("--pullrequest") - { - Description = "If available, the number of the pull request being built.", - DefaultValueFactory = parseResult => null - }; - Option ownerOption = new("--owner") - { - Description = "If available, the owner organization of the repository.", - DefaultValueFactory = parseResult => null - }; - Option repoOption = new("--repo") - { - Description = "If available, the name of the repository.", - DefaultValueFactory = parseResult => null - }; - Option dryrunTestIdOption = new("--dryrun-test-id") - { - Description = "The test id from data.json to simulate a pull request.", - DefaultValueFactory = parseResult => null - }; - Option dryrunTestDataFileOption = new("--dryrun-test-data-file") - { - Description = "The json file defining all the tests that can be referenced by `dryrunTestId`. Usually data.json.", - DefaultValueFactory = parseResult => null - }; - RootCommand rootCommand = new("Snippets5000 CI build application."); - - rootCommand.Options.Add(sourcePathOption); - rootCommand.Options.Add(pullrequestOption); - rootCommand.Options.Add(ownerOption); - rootCommand.Options.Add(repoOption); - rootCommand.Options.Add(dryrunTestIdOption); - rootCommand.Options.Add(dryrunTestDataFileOption); - - ParseResult result = rootCommand.Parse(args); - foreach (ParseError parseError in result.Errors) - { - Console.Error.WriteLine(parseError.Message); - } - if (result.Errors.Count > 0) - { - throw new InvalidOperationException("Invalid command line."); - } - var sourcepath = result.GetValue(sourcePathOption) ?? throw new InvalidOperationException("organization is null"); - var pullrequest = result.GetValue(pullrequestOption); - var owner = result.GetValue(ownerOption); - var repo = result.GetValue(repoOption); - var dryrunTestId = result.GetValue(dryrunTestIdOption); - var dryrunTestDataFile = result.GetValue(dryrunTestDataFileOption); - return (sourcepath, pullrequest, owner, repo, dryrunTestId, dryrunTestDataFile); - } } From cfd25916359d7ec099fbd16ec6de74bc3a7c271f Mon Sep 17 00:00:00 2001 From: "Andy De George (from Dev Box)" Date: Wed, 25 Feb 2026 15:30:17 -0800 Subject: [PATCH 2/4] Delete old PowerShell script --- snippets5000/Get-MSBuildResults.ps1 | 352 ------------------------ snippets5000/Out-GithubActionStatus.ps1 | 79 ------ 2 files changed, 431 deletions(-) delete mode 100644 snippets5000/Get-MSBuildResults.ps1 delete mode 100644 snippets5000/Out-GithubActionStatus.ps1 diff --git a/snippets5000/Get-MSBuildResults.ps1 b/snippets5000/Get-MSBuildResults.ps1 deleted file mode 100644 index 9d7480ba..00000000 --- a/snippets5000/Get-MSBuildResults.ps1 +++ /dev/null @@ -1,352 +0,0 @@ -<# - -.SYNOPSIS - Invokes dotnet build on the samples sln and project files. - -.DESCRIPTION - Invokes dotnet build on the samples sln and project files. - -.PARAMETER RepoRootDir - The directory of the repository files on the local machine. - -.PARAMETER PullRequest - The pull request to process. If 0 or not passed, processes the whole repo - -.PARAMETER RepoOwner - The name of the repository owner. - -.PARAMETER RepoName - The name of the repository. - -.PARAMETER RangeStart - A range of results to process. - -.PARAMETER RangeEnd - A range of results to process. - -.INPUTS - None - -.OUTPUTS - None - -.NOTES - - Version: 1.8 - Author: adegeo@microsoft.com - Creation Date: 12/11/2020 - Update Date: 10/05/2022 - Purpose/Change: Add support for discovering and processing settings file for project errors (not found, too many, etc) - - Version: 1.7 - Author: adegeo@microsoft.com - Creation Date: 12/11/2020 - Update Date: 09/26/2022 - Purpose/Change: Trim build error lines to help remove duplicates. - - Version: 1.6 - Author: adegeo@microsoft.com - Creation Date: 12/11/2020 - Update Date: 03/10/2022 - Purpose/Change: Export proj/sln settings config to output.json file. -#> - -[CmdletBinding()] -Param( - [Parameter(Mandatory = $true, ValueFromPipeline = $false)] - [System.String] $RepoRootDir = $env:RepoRootDir, - - [Parameter(Mandatory = $false, ValueFromPipeline = $false)] - [System.Int64] $PullRequest = 0, - - [Parameter(Mandatory = $false, ValueFromPipeline = $false)] - [System.String] $RepoOwner = "", - - [Parameter(Mandatory = $false, ValueFromPipeline = $false)] - [System.String] $RepoName = "", - - [Parameter(Mandatory = $false, ValueFromPipeline = $false)] - [System.Int32] $RangeStart = $env:rangestart, - - [Parameter(Mandatory = $false, ValueFromPipeline = $false)] - [System.Int32] $RangeEnd = $env:rangeend -) - -$Global:statusOutput = @() - -Write-Host "Gathering solutions and projects... (v1.8)" - -if ($PullRequest -ne 0) { - Write-Host "Running `"LocateProjects `"$RepoRootDir`" --pullrequest $PullRequest --owner $RepoOwner --repo $RepoName`"" - $output = Invoke-Expression "LocateProjects `"$RepoRootDir`" --pullrequest $PullRequest --owner $RepoOwner --repo $RepoName" -} -else { - Write-Host "Running `"LocateProjects `"$RepoRootDir`"" - $output = Invoke-Expression "LocateProjects `"$RepoRootDir`"" -} - -if ($LASTEXITCODE -ne 0) -{ - $output - throw "Error on running LocateProjects" -} - -function New-Result($inputFile, $projectFile, $exitcode, $outputText, $settingsJson) -{ - $info = @{} - - $info.InputFile = $inputFile - $info.ProjectFile = $projectFile - $info.ExitCode = $exitcode - $info.Output = $outputText - $info.Settings = $settingsJson - - $object = New-Object -TypeName PSObject -Prop $info - $Global:statusOutput += $object -} - -$workingSet = $output - -if (($RangeStart -ne 0) -and ($RangeEnd -ne 0)){ - $workingSet = $output[$RangeStart..$RangeEnd] -} - -# Log working set items prior to filtering -$workingSet | Write-Host - -# Remove duplicated projects and skip snippets files from being processed -$projects = @() -$workingSetTemp = @() - -foreach ($item in $workingSet) { - $data = $item.Split('|') - if ($projects.Contains($data[2].Trim()) -or $data[1].EndsWith("snippets.5000.json")) { - continue - } - if ($data[2].Trim() -ne "") { - $projects += $data[2].Trim() - } - $workingSetTemp += $item -} - -$workingSet = $workingSetTemp - -# Process working set -$counter = 1 -$length = $workingSet.Count -$thisExitCode = 0 - -$ErrorActionPreference = "Continue" - -foreach ($item in $workingSet) { - try { - Write-Host "$counter/$length :: $Item" - - $data = $item.Split('|') - - # Project found, build it - if ([int]$data[0] -eq 0) { - $projectFile = Resolve-Path "$RepoRootDir\$($data[2])" - $configFile = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($projectFile), "snippets.5000.json") - $settings = $null - - # Create the default build command - "dotnet build `"$projectFile`"" | Out-File ".\run.bat" - - # Check for config file - if ([System.IO.File]::Exists($configFile) -eq $true) { - Write-Host "- Config file found" - - $settings = $configFile | Get-ChildItem | Get-Content | ConvertFrom-Json - - if ($settings.host -eq "visualstudio") { - Write-Host "- Using visual studio as build host" - - # Create the visual studio build command - "CALL `"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat`"`n" + - "nuget.exe restore `"$projectFile`"`n" + - "msbuild.exe `"$projectFile`" -restore:True" ` - | Out-File ".\run.bat" - } - elseif ($settings.host -eq "custom") { - Write-Host "- Using custom build host: $($settings.command)" - - $ExecutionContext.InvokeCommand.ExpandString($settings.command) | Out-File ".\run.bat" - } - elseif ($settings.host -eq "dotnet") { - Write-Host "- Using dotnet build host" - - "dotnet build `"$projectFile`"" | Out-File ".\run.bat" - } - else { - throw "snippets.5000.json file isn't valid." - } - } - - Write-Host "run.bat contents: " - Get-Content .\run.bat | Write-Host - Write-Host - - $thisExitCode = 0 - - Invoke-Expression ".\run.bat" | Out-String | Tee-Object -Variable "result" - - if ($LASTEXITCODE -ne 0) { - $thisExitCode = 4 - } - - New-Result $data[1] $projectFile $thisExitCode $result $settings - } - - # No project found - else - { - $settings = $null - $filePath = Resolve-Path "$RepoRootDir\$($data[1])" - - # Hunt for snippets config file - do { - - $configFile = [System.IO.Path]::Combine($filePath, "snippets.5000.json") - - if ([System.IO.File]::Exists($configFile) -eq $true) { - - $settings = $configFile | Get-ChildItem | Get-Content | ConvertFrom-Json - Write-Host "Loading settings for errors found by LocateProjects: $configFile" - break - } - - # go back one folder - $filePath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($filePath, "..\")) - } until ([System.Linq.Enumerable]::Count($filePath, [Func[Char, Boolean]] { param($x) $x -eq '\' }) -eq 1) - - if ($settings -eq $null) { - Write-Host "No settings file found for LocateProjects reported error" - } - - # Process each error - if ([int]$data[0] -eq 1) { - New-Result $data[1] "" 1 "ERROR: Project missing. A project (and optionally a solution file) must be in this directory or one of the parent directories to validate and build this code." $settings - - $thisExitCode = 1 - } - - # Too many projects found - elseif ([int]$data[0] -eq 2) { - New-Result $data[1] $data[2] 2 "ERROR: Too many projects found. A single project or solution must exist in this directory or one of the parent directories." $settings - - $thisExitCode = 2 - } - - # Solution found, but no project - elseif ([int]$data[0] -eq 3) { - New-Result $data[1] $data[2] 2 "ERROR: Solution found, but missing project. A project is required to compile this code." $settings - $thisExitCode = 3 - } - - } - - } - catch { - New-Result $data[1] $projectFile 1000 "ERROR: $($_.Exception)" $null - $thisExitCode = 4 - Write-Host $_.Exception.Message -Foreground "Red" - Write-Host $_.ScriptStackTrace -Foreground "DarkGray" - } - - $counter++ -} - -$resultItems = $Global:statusOutput | Select-Object InputFile, ProjectFile, ExitCode, Output, Settings - -# Add our output type -$typeResult = @" -public class ResultItem -{ - public string ProjectFile; - public string InputFile; - public int ExitCode; - public string BuildOutput; - public object Settings; - public MSBuildError[] Errors; - public int ErrorCount; - - public class MSBuildError - { - public string Line; - public string Error; - } -} -"@ -Add-Type $typeResult - -$transformedItems = $resultItems | ForEach-Object { New-Object ResultItem -Property @{ - ProjectFile = $_.ProjectFile.Path; - InputFile = $_.InputFile; - ExitCode = $_.ExitCode; - BuildOutput = $_.Output; - Settings = $_.Settings; - Errors = @(); - ErrorCount = 0; - } } - -# Transform the build output to break it down into MSBuild result entries -foreach ($item in $transformedItems) { - $list = @() - - # Clean - if ($item.ExitCode -eq 0) { - #$list += New-Object -TypeName "ResultItem+MSBuildError" -Property @{ Line = $item.BuildOutput; Error = $item.BuildOutput } - } - # No project found - # Too many projects found - # Solution found, but no project - elseif ($item.ExitCode -ne 4) { - $list += New-Object -TypeName "ResultItem+MSBuildError" -Property @{ Line = $item.BuildOutput; Error = $item.BuildOutput } - $item.ErrorCount = 1 - } - - # Actual build error found - else { - $errorInfo = $item.BuildOutput -Split [System.Environment]::NewLine | - Select-String ": (?:Solution file error|error) ([^:]*)" | ` - Select-Object Line -ExpandProperty Matches | ` - Select-Object -Property @{Name = 'Line'; Expression = {$_.Line.Trim()}}, Groups | ` - Sort-Object Line | Get-Unique -AsString - $item.ErrorCount = $errorInfo.Count - foreach ($err in $errorInfo) { - $list += New-Object -TypeName "ResultItem+MSBuildError" -Property @{ Line = $err.Line; Error = $err.Groups[1].Value } - } - - # Error count of 0 here means that no error was detected from build results, but there was still a failure of some kind - if ($item.ErrorCount -eq 0) { - $list += New-Object -TypeName "ResultItem+MSBuildError" -Property @{ Line = "Unknown error occurred. Check log and build output."; Error = "4" } - $item.ErrorCount = 1 - } - } - - # Set build errors - $item.Errors = $list - -} - -$transformedItems | ConvertTo-Json -Depth 4 | Out-File 'output.json' - -exit 0 - - -# Sample snippets.5000.json file -<# -{ - "host": "visualstudio", - "expectederrors": [ - { - "file": "samples/snippets/csharp/VS_Snippets_VBCSharp/csprogguideindexedproperties/cs/Program.cs", - "line": 5, - "column": 25, - "error": "CS0234" - } - ] -} - -#> diff --git a/snippets5000/Out-GithubActionStatus.ps1 b/snippets5000/Out-GithubActionStatus.ps1 deleted file mode 100644 index b116a856..00000000 --- a/snippets5000/Out-GithubActionStatus.ps1 +++ /dev/null @@ -1,79 +0,0 @@ -<# - -.SYNOPSIS - Reads the output.json file and outputs status to GitHub Actions - -.DESCRIPTION - Reads the output.json file and outputs status to GitHub Actions - -.INPUTS - None - -.OUTPUTS - None - -.NOTES - Version: 1.2 - Author: adegeo@microsoft.com - Creation Date: 06/24/2020 - Update Date: 03/10/2022 - Purpose/Change: Support ignoring known errors. -#> - -[CmdletBinding()] -Param( -) - -$json = Get-Content output.json | ConvertFrom-Json - -$errors = $json | Where-Object ErrorCount -ne 0 | Select-Object InputFile, Settings -ExpandProperty Errors | Select-Object InputFile, Settings, Error, Line - -# Exit if no error entries were found -$count = $errors.Count - -if ($count -eq 0) { - Write-Host "All builds passed" - exit 0 -} - -Write-Host "Total errors: $count" - -foreach ($er in $errors) { - - $skipError = $false - - $lineColMatch = $er.Line | Select-String "(^.*)\((\d*),(\d*)\)" | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Groups - $errorFile = $er.InputFile - $errorLineNumber = 0 - $errorColNumber = 0 - - if ($lineColMatch.Count -eq 4) { - $errorFile = $lineColMatch[1].Value.Replace("D:\a\$($env:repo)\$($env:repo)\", "").Replace("\", "/") - $errorLineNumber = $lineColMatch[2].Value - $errorColNumber = $lineColMatch[3].Value - } - - # Check if there are any errors that should be skipped because they're known failures - foreach ($expectedError in $er.Settings.expectederrors) { - if (($expectedError.file -eq $errorFile) -and ($expectedError.error -eq $er.error)) { - Write-Host "Skipping error:`n- File: $errorFile`n- Error: $($er.error)" - $skipError = $true - break - } - } - - if ($skipError -eq $false) { - Write-Host "::error file=$errorFile,line=$errorLineNumber,col=$errorColNumber::$($er.Line)" - } - else { - $count -= 1 - } -} - -Write-Host "Errors after skips: $count" - -if ($count -eq 0) { - exit 0 -} - -exit 1 From 8228118ceb767f781593908b03ca2ba6698dba15 Mon Sep 17 00:00:00 2001 From: "Andy De George (from Dev Box)" Date: Thu, 26 Feb 2026 12:44:49 -0800 Subject: [PATCH 3/4] Dryrun is now a subcommand --- snippets5000/Snippets5000/Program.cs | 227 +++++++++++------- .../Properties/launchSettings.json | 18 +- .../Snippets5000/PullRequestProcessor.cs | 2 +- 3 files changed, 147 insertions(+), 100 deletions(-) diff --git a/snippets5000/Snippets5000/Program.cs b/snippets5000/Snippets5000/Program.cs index 310cc39c..87679744 100644 --- a/snippets5000/Snippets5000/Program.cs +++ b/snippets5000/Snippets5000/Program.cs @@ -41,30 +41,28 @@ class Program /// 0 on success. Otherwise, a non-zero error code. static async Task Main(string[] args) { + // Shared option for all commands Option sourcePathOption = new("--sourcepath") { Description = "The directory containing the local source tree.", Required = true }; - Option pullrequestOption = new("--pullrequest") - { - Description = "If available, the number of the pull request being built." - }; - Option ownerOption = new("--owner") - { - Description = "If available, the owner organization of the repository." - }; - Option repoOption = new("--repo") + + // Root command options (PR mode - default) + Option pullrequestOption = new("--pullrequest") { - Description = "If available, the name of the repository." + Description = "The number of the pull request being built.", + Required = true }; - Option dryrunTestIdOption = new("--dryrun-test-id") + Option ownerOption = new("--owner") { - Description = "The test id from data.json to simulate a pull request." + Description = "The owner organization of the repository.", + Required = true }; - Option dryrunTestDataFileOption = new("--dryrun-test-data-file") + Option repoOption = new("--repo") { - Description = "The json file defining all the tests that can be referenced by `dryrunTestId`. Usually data.json." + Description = "The name of the repository.", + Required = true }; RootCommand rootCommand = new("Snippets5000 CI build application.") @@ -72,115 +70,164 @@ static async Task Main(string[] args) sourcePathOption, pullrequestOption, ownerOption, - repoOption, - dryrunTestIdOption, - dryrunTestDataFileOption + repoOption }; rootCommand.SetAction(async (parseResult, cancellationToken) => { var sourcepath = parseResult.GetValue(sourcePathOption)!; var pullrequest = parseResult.GetValue(pullrequestOption); - var owner = parseResult.GetValue(ownerOption); - var repo = parseResult.GetValue(repoOption); - var dryrunTestId = parseResult.GetValue(dryrunTestIdOption); - var dryrunTestDataFile = parseResult.GetValue(dryrunTestDataFileOption); + var owner = parseResult.GetValue(ownerOption)!; + var repo = parseResult.GetValue(repoOption)!; + + Console.WriteLine($"Processing source path: {sourcepath}"); + Console.WriteLine($"Processing PR #{pullrequest} from {owner}/{repo}"); + + return await RunPullRequestAsync(sourcepath, pullrequest, owner, repo); + }); + + // Dryrun subcommand - simulates a PR using local test data + Option testIdOption = new("--test-id") + { + Description = "The test id from the test data file to simulate a pull request.", + Required = true + }; + Option testDataFileOption = new("--test-data-file") + { + Description = "The json file defining all the tests that can be referenced by test-id. Usually data.json.", + Required = true + }; + + Command dryrunCommand = new("dryrun", "Simulate a pull request using local test data.") + { + sourcePathOption, + testIdOption, + testDataFileOption + }; + + dryrunCommand.SetAction(async (parseResult, cancellationToken) => + { + var sourcepath = parseResult.GetValue(sourcePathOption)!; + var testId = parseResult.GetValue(testIdOption)!; + var testDataFile = parseResult.GetValue(testDataFileOption)!; + + Console.WriteLine($"Processing source path: {sourcepath}"); + Console.WriteLine($"Running dryrun test: {testId}"); + + return await RunDryrunAsync(sourcepath, testId, testDataFile); + }); + + rootCommand.Subcommands.Add(dryrunCommand); + + // Build-all subcommand - builds the entire repository + Command buildAllCommand = new("build-all", "Build all projects in the repository.") + { + sourcePathOption + }; + + buildAllCommand.SetAction(async (parseResult, cancellationToken) => + { + var sourcepath = parseResult.GetValue(sourcePathOption)!; Console.WriteLine($"Processing source path: {sourcepath}"); + Console.WriteLine("Building all projects in repository..."); - return await RunAsync(sourcepath, pullrequest, owner, repo, dryrunTestId, dryrunTestDataFile); + return await RunBuildAllAsync(sourcepath); }); + rootCommand.Subcommands.Add(buildAllCommand); + return await rootCommand.Parse(args).InvokeAsync(); } - private static async Task RunAsync( - string sourcepath, - int? pullrequest, - string? owner, - string? repo, - string? dryrunTestId, - string? dryrunTestDataFile) + /// + /// Process a GitHub pull request. + /// + private static async Task RunPullRequestAsync(string sourcepath, int pullrequest, string owner, string repo) { + var key = CommandLineUtility.GetEnvVariable("GitHubKey", "You must store your GitHub key in the 'GitHubKey' environment variable", null); - int exitCode = EXITCODE_GOOD; - string appStartupFolder = Directory.GetCurrentDirectory(); + List projects = []; + await foreach (var item in new PullRequestProcessor(owner, repo, pullrequest, sourcepath).GenerateBuildList(key)) + projects.Add(item); - if ((pullrequest.HasValue) && - !string.IsNullOrEmpty(owner) && - !string.IsNullOrEmpty(repo)) - { - List projects; + return await ProcessAndCompileProjectsAsync(sourcepath, repo, projects); + } - // Normal github PR - if (string.IsNullOrEmpty(dryrunTestId)) - { - var key = CommandLineUtility.GetEnvVariable("GitHubKey", "You must store your GitHub key in the 'GitHubKey' environment variable", null); + /// + /// Simulate a pull request using local test data. + /// + private static async Task RunDryrunAsync(string sourcepath, string testId, string testDataFile) + { + List projects = new TestingProjectList(testId, testDataFile, sourcepath) + .GenerateBuildList() + .ToList(); - List localResults = new(); - await foreach (var item in new PullRequestProcessor(owner, repo, pullrequest.Value, sourcepath).GenerateBuildList(key)) - localResults.Add(item); + // Use a placeholder repo name for error path processing + return await ProcessAndCompileProjectsAsync(sourcepath, "dryrun", projects); + } - projects = localResults; - } + /// + /// Build all projects in the repository. + /// + private static Task RunBuildAllAsync(string sourcepath) + { + // TODO: Implement full build with compile step + var fullBuild = new FullBuildProjectList(sourcepath); + foreach (var path in fullBuild.GenerateBuildList()) + Log.Write(0, path); - // NOT a normal github PR and instead is a test - else if (string.IsNullOrEmpty(dryrunTestDataFile)) - throw new InvalidOperationException($"{nameof(dryrunTestDataFile)}: The dryrun Test DataFile must be set"); - else - projects = new TestingProjectList(dryrunTestId, dryrunTestDataFile, sourcepath).GenerateBuildList().ToList(); + return Task.FromResult(EXITCODE_GOOD); + } - Log.Write(0, $"{projects.Count} items found."); - Log.Write(0, "\r\nOutput all items found, grouped by status..."); + /// + /// Shared logic for processing and compiling discovered projects. + /// + private static async Task ProcessAndCompileProjectsAsync(string sourcepath, string repo, List projects) + { + int exitCode = EXITCODE_GOOD; + string appStartupFolder = Directory.GetCurrentDirectory(); - // Start processing all of the discovered projects - ProcessDiscoveredProjects(projects, out List transformedProjects, out string[] projectsToCompile); + Log.Write(0, $"{projects.Count} items found."); + Log.Write(0, "\r\nOutput all items found, grouped by status..."); - // Compile each project - await CompileProjects(sourcepath, projectsToCompile, transformedProjects); + // Start processing all of the discovered projects + ProcessDiscoveredProjects(projects, out List transformedProjects, out string[] projectsToCompile); - // Clear any known errors from the failed projects - ProcessFailedProjects(repo, transformedProjects.Where(p => p.RunExitCode != 0)); + // Compile each project + await CompileProjects(sourcepath, projectsToCompile, transformedProjects); - // Final results. List the projects/files that have failed - bool first = false; - var finalFailedProjects = transformedProjects.Where(p => !p.RunConsideredGood).ToArray(); - foreach (var item in finalFailedProjects) - { - if (!first) - { - Log.Write(0, "\r\n๐Ÿ˜ญ Compile targets with unresolved issues:"); - first = true; - } - Log.Write(2, item.RunTargetFile); - exitCode = EXITCODE_BAD; - } + // Clear any known errors from the failed projects + ProcessFailedProjects(repo, transformedProjects.Where(p => p.RunExitCode != 0)); - // Generate output file - if (finalFailedProjects.Length != 0) + // Final results. List the projects/files that have failed + bool first = false; + var finalFailedProjects = transformedProjects.Where(p => !p.RunConsideredGood).ToArray(); + foreach (var item in finalFailedProjects) + { + if (!first) { - Directory.SetCurrentDirectory(appStartupFolder); - JsonSerializerOptions options = new JsonSerializerOptions() { WriteIndented = true, ReadCommentHandling = JsonCommentHandling.Skip }; - using FileStream file = File.Open("output.json", FileMode.Create); - JsonSerializer.Serialize(file, finalFailedProjects, options); + Log.Write(0, "\r\n๐Ÿ˜ญ Compile targets with unresolved issues:"); + first = true; } - - // There were no errors, log it! - if (exitCode == 0) - Log.Write(0, "\r\n๐Ÿ˜€ All builds passing! ๐Ÿ˜€"); - - return exitCode; + Log.Write(2, item.RunTargetFile); + exitCode = EXITCODE_BAD; } - // TODO: building the whole repository - else + // Generate output file + if (finalFailedProjects.Length != 0) { - var fullBuild = new FullBuildProjectList(sourcepath); - foreach (var path in fullBuild.GenerateBuildList()) - Log.Write(0, path); + Directory.SetCurrentDirectory(appStartupFolder); + JsonSerializerOptions options = new() { WriteIndented = true, ReadCommentHandling = JsonCommentHandling.Skip }; + using FileStream file = File.Open("output.json", FileMode.Create); + JsonSerializer.Serialize(file, finalFailedProjects, options); } - return EXITCODE_GOOD; + // There were no errors, log it! + if (exitCode == 0) + Log.Write(0, "\r\n๐Ÿ˜€ All builds passing! ๐Ÿ˜€"); + + return exitCode; } // Takes the discovery results from scanning the files in the PR and checks their status. The projects @@ -254,7 +301,7 @@ private static async Task CompileProjects(string sourcePath, string[] projectsTo foreach (var item in projectsToCompile) { expansionVariables.Clear(); - + Directory.SetCurrentDirectory(sourcePath); Log.CreateGroup($"Compile: {counter}/{projectsToCompile.Length} {item}"); diff --git a/snippets5000/Snippets5000/Properties/launchSettings.json b/snippets5000/Snippets5000/Properties/launchSettings.json index 2bd80190..21db4d18 100644 --- a/snippets5000/Snippets5000/Properties/launchSettings.json +++ b/snippets5000/Snippets5000/Properties/launchSettings.json @@ -1,9 +1,9 @@ { "profiles": { "Test via GitHub": { - "commandName": "Project", - "commandLineArgs": "--sourcepath C:\\Users\\adegeo\\code\\dotnet-api-docs --owner dotnet --repo dotnet-api-docs --pullrequest 11502", - "environmentVariables": { + "commandName": "Project", + "commandLineArgs": "--sourcepath C:\\Users\\adegeo\\code\\dotnet\\dotnet-docs --owner dotnet --repo docs --pullrequest 51928", + "environmentVariables": { "ExtensionsProjs": ".csproj;.fsproj;.vbproj;.vcxproj;.sln;.slnx", "ExtensionsCodeTriggers": ".cs;.vb;.fs;.cpp;.h;.xaml;.razor;.cshtml;.vbhtml", "FileTriggers": "global.json;snippets.5000.json", @@ -11,14 +11,14 @@ } }, "Test via Local Tests": { - "commandName": "Project", - "commandLineArgs": "--dryrun-test-id \"Edit - Solution found, no project\" --sourcepath C:\\Users\\adegeo\\code\\dotnet\\docs-tools\\snippets5000\\PullRequestSimulations --owner nah --repo nah --pullrequest 1 --dryrun-test-data-file C:\\Users\\adegeo\\code\\dotnet\\docs-tools\\snippets5000\\PullRequestSimulations\\data.json", - "environmentVariables": { + "commandName": "Project", + "commandLineArgs": "dryrun --test-id \"Edit - Solution found, no project\" --sourcepath C:\\Users\\adegeo\\code\\dotnet\\docs-tools\\snippets5000\\PullRequestSimulations --test-data-file C:\\Users\\adegeo\\code\\dotnet\\docs-tools\\snippets5000\\PullRequestSimulations\\data.json", + "environmentVariables": { "ExtensionsProjs": ".csproj;.fsproj;.vbproj;.vcxproj;.sln;slnx", - "ExtensionsCodeTriggers": ".cs;.vb;.fs;.cpp;.h;.xaml;.razor;.cshtml;.vbhtml", - "FileTriggers": "global.json;snippets.5000.json", + "ExtensionsCodeTriggers": ".cs;.vb;.fs;.cpp;.h;.xaml;.razor;.cshtml;.vbhtml", + "FileTriggers": "global.json;snippets.5000.json", "VS_DEVCMD": "C:\\Program Files\\Microsoft Visual Studio\\2026\\Community\\Common7\\Tools\\VsDevCmd.bat" - } + } } } } diff --git a/snippets5000/Snippets5000/PullRequestProcessor.cs b/snippets5000/Snippets5000/PullRequestProcessor.cs index 51c2b4a4..9895eb53 100644 --- a/snippets5000/Snippets5000/PullRequestProcessor.cs +++ b/snippets5000/Snippets5000/PullRequestProcessor.cs @@ -72,7 +72,7 @@ private async IAsyncEnumerable FindAllSolutionsAndProjects(stri static internal DiscoveryResult? GenerateItemResult(string rootDir, string item) { // Get components of the file path - string fullPath = Path.Combine(rootDir, item); + string fullPath = Path.GetFullPath(Path.Combine(rootDir, item)); string itemFileName = Path.GetFileName(fullPath); string itemPath = Path.GetDirectoryName(fullPath)!; From 135b25e2613c08d1c15e59cf61914938708551c7 Mon Sep 17 00:00:00 2001 From: "Andy De George (from Dev Box)" Date: Thu, 26 Feb 2026 12:51:23 -0800 Subject: [PATCH 4/4] Fix indentation --- .../Properties/launchSettings.json | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/snippets5000/Snippets5000/Properties/launchSettings.json b/snippets5000/Snippets5000/Properties/launchSettings.json index 21db4d18..683f10f3 100644 --- a/snippets5000/Snippets5000/Properties/launchSettings.json +++ b/snippets5000/Snippets5000/Properties/launchSettings.json @@ -1,24 +1,24 @@ { "profiles": { "Test via GitHub": { - "commandName": "Project", - "commandLineArgs": "--sourcepath C:\\Users\\adegeo\\code\\dotnet\\dotnet-docs --owner dotnet --repo docs --pullrequest 51928", - "environmentVariables": { - "ExtensionsProjs": ".csproj;.fsproj;.vbproj;.vcxproj;.sln;.slnx", + "commandName": "Project", + "commandLineArgs": "--sourcepath C:\\Users\\adegeo\\code\\dotnet\\dotnet-docs --owner dotnet --repo docs --pullrequest 51928", + "environmentVariables": { + "ExtensionsProjs": ".csproj;.fsproj;.vbproj;.vcxproj;.sln;.slnx", "ExtensionsCodeTriggers": ".cs;.vb;.fs;.cpp;.h;.xaml;.razor;.cshtml;.vbhtml", "FileTriggers": "global.json;snippets.5000.json", - "VS_DEVCMD": "C:\\Program Files\\Microsoft Visual Studio\\2026\\Community\\Common7\\Tools\\VsDevCmd.bat" + "VS_DEVCMD": "C:\\Program Files\\Microsoft Visual Studio\\2026\\Community\\Common7\\Tools\\VsDevCmd.bat" } }, "Test via Local Tests": { - "commandName": "Project", - "commandLineArgs": "dryrun --test-id \"Edit - Solution found, no project\" --sourcepath C:\\Users\\adegeo\\code\\dotnet\\docs-tools\\snippets5000\\PullRequestSimulations --test-data-file C:\\Users\\adegeo\\code\\dotnet\\docs-tools\\snippets5000\\PullRequestSimulations\\data.json", - "environmentVariables": { - "ExtensionsProjs": ".csproj;.fsproj;.vbproj;.vcxproj;.sln;slnx", - "ExtensionsCodeTriggers": ".cs;.vb;.fs;.cpp;.h;.xaml;.razor;.cshtml;.vbhtml", - "FileTriggers": "global.json;snippets.5000.json", - "VS_DEVCMD": "C:\\Program Files\\Microsoft Visual Studio\\2026\\Community\\Common7\\Tools\\VsDevCmd.bat" - } + "commandName": "Project", + "commandLineArgs": "dryrun --test-id \"Edit - Solution found, no project\" --sourcepath C:\\Users\\adegeo\\code\\dotnet\\docs-tools\\snippets5000\\PullRequestSimulations --test-data-file C:\\Users\\adegeo\\code\\dotnet\\docs-tools\\snippets5000\\PullRequestSimulations\\data.json", + "environmentVariables": { + "ExtensionsProjs": ".csproj;.fsproj;.vbproj;.vcxproj;.sln;slnx", + "ExtensionsCodeTriggers": ".cs;.vb;.fs;.cpp;.h;.xaml;.razor;.cshtml;.vbhtml", + "FileTriggers": "global.json;snippets.5000.json", + "VS_DEVCMD": "C:\\Program Files\\Microsoft Visual Studio\\2026\\Community\\Common7\\Tools\\VsDevCmd.bat" + } } } }