diff --git a/IssueCloser/IssueCloser.csproj b/IssueCloser/IssueCloser.csproj
index 50d66b92..c7fe5684 100644
--- a/IssueCloser/IssueCloser.csproj
+++ b/IssueCloser/IssueCloser.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/IssueCloser/Program.cs b/IssueCloser/Program.cs
index b81b3f5e..ffd1cd5f 100644
--- a/IssueCloser/Program.cs
+++ b/IssueCloser/Program.cs
@@ -1,177 +1,194 @@
-using DotNetDocs.Tools.Utility;
-using DotNetDocs.Tools.GitHubCommunications;
-using DotNetDocs.Tools.GraphQLQueries;
-using Microsoft.DotnetOrg.Ospo;
+using System.CommandLine;
using System.Text.Json;
using DotNet.DocsTools.GitHubObjects;
using DotNet.DocsTools.GraphQLQueries;
+using DotNetDocs.Tools.GitHubCommunications;
+using DotNetDocs.Tools.GraphQLQueries;
+using DotNetDocs.Tools.Utility;
+using Microsoft.DotnetOrg.Ospo;
+using IssueCloser;
+using System.CommandLine.Parsing;
-namespace IssueCloser;
-
-class Program
-{
- private const string ConfigFile = "bulkcloseconfig.json";
- // shoag, rpetrusha worked with us, but have retired:
- private static readonly string[] teamAuthors = ["shoag", "rpetrusha"];
+const string ConfigFile = "bulkcloseconfig.json";
- // The text of the comment to add:
- private const string commentText =
+// The text of the comment to add:
+const string commentText =
@"This issue has been closed as part of the issue backlog grooming process outlined in #22351.
That automated process may have closed some issues that should be addressed. If you think this is one of them, reopen it with a comment explaining why. Tag the `@dotnet/docs` team for visibility.";
- ///
- /// Close issues based on age, author (customer or MS employee) and priority labels.
- /// This was written for (hopefully) a one-time situation. The dotnet/docs repo
- /// reached over 1500 issues. We couldn't plan effectively, and we bulk closed
- /// a number of issues
- /// The tool search issues for candidates based on age, if the author is a customer
- /// or MS employee, and priority labels. Issues that are over the threshold are closed.
- /// In addition, those issues have a comment pointing to a master issue that describes
- /// the process.
- ///
- /// The organization managing the repo.
- /// The repository to update.
- /// True to list work, but not actually close any issue.
- /// 0 on success, a non-zero number on error conditions.
- static async Task Main(string organization = "dotnet", string repository = "docs", bool dryRun = false)
- {
- var key = CommandLineUtility.GetEnvVariable("GitHubBotKey",
- "You must store the bot's GitHub key in the 'GitHubBotKey' environment variable",
- "");
- var ospoKey = CommandLineUtility.GetEnvVariable("OspoKey",
- "You must store your OSPO key in the 'OspoKey' environment variable",
- "");
+var (organization, repository, dryRun) = ParseArguments(args);
- var client = IGitHubClient.CreateGitHubClient(key);
- var ospoClient = new OspoClient(ospoKey, true);
+var key = CommandLineUtility.GetEnvVariable("GitHubBotKey",
+ "You must store the bot's GitHub key in the 'GitHubBotKey' environment variable",
+ "");
+var ospoKey = CommandLineUtility.GetEnvVariable("OspoKey",
+ "You must store your OSPO key in the 'OspoKey' environment variable",
+ "");
- var labelQuery = new ScalarQuery(client);
+var client = IGitHubClient.CreateGitHubClient(key);
+var ospoClient = new OspoClient(ospoKey, true);
- var label = await labelQuery.PerformQuery(new FindLabelQueryVariables(organization, repository, "won't fix"));
- if (label is null)
- {
- Console.WriteLine($"Could not find label [won't fix]");
- return -1;
- }
- var labelID = label.Id;
+var labelQuery = new ScalarQuery(client);
- try
- {
- // Next, starting paging through all issues:
- Console.WriteLine("Processing open issues");
- await ProcessIssues(client, ospoClient, organization, repository, dryRun, labelID);
- return 0;
- }
- catch (InvalidOperationException e)
- {
- Console.WriteLine(e.Message);
- return -1;
- }
- }
+var label = await labelQuery.PerformQuery(new FindLabelQueryVariables(organization, repository, "won't fix"));
+if (label is null)
+{
+ Console.WriteLine($"Could not find label [won't fix]");
+ return -1;
+}
+var labelID = label.Id;
- private static async Task ProcessIssues(IGitHubClient client, OspoClient ospoClient, string organization, string repository, bool dryRun, string labelID)
- {
- var query = new EnumerationQuery(client);
- var now = DateTime.Now;
+try
+{
+ // Next, starting paging through all issues:
+ Console.WriteLine("Processing open issues");
+ await ProcessIssues(client, ospoClient, organization, repository, dryRun, labelID);
+ return 0;
+}
+catch (InvalidOperationException e)
+{
+ Console.WriteLine(e.Message);
+ return -1;
+}
- var stats = await BuildStatsMapAsync();
+static async Task ProcessIssues(IGitHubClient client, OspoClient ospoClient, string organization, string repository, bool dryRun, string labelID)
+{
+ var query = new EnumerationQuery(client);
+ var now = DateTime.Now;
+
+ var stats = await BuildStatsMapAsync();
- int totalClosedIssues = 0;
- int totalIssues = 0;
- await foreach (var item in query.PerformQuery(new BankruptcyIssueVariables(organization, repository)))
+ int totalClosedIssues = 0;
+ int totalIssues = 0;
+ await foreach (var item in query.PerformQuery(new BankruptcyIssueVariables(organization, repository)))
+ {
+ var issueID = item.Id;
+
+ var priority = Priorities.PriLabel(item.Labels);
+ bool isInternal = await item.Author.IsMicrosoftFTE(ospoClient) == true;
+ bool isDocIssue = IsDocsIssue(item.Body);
+ int ageInMonths = (int)(now - item.CreatedDate).TotalDays / 30;
+ var criteria = new CloseCriteria(priority, isDocIssue, isInternal);
+ var number = item.Number;
+ var title = item.Title;
+
+ totalIssues++;
+ if (stats[criteria].ShouldCloseIssue(criteria, ageInMonths))
{
- var issueID = item.Id;
-
- var priority = Priorities.PriLabel(item.Labels);
- bool isInternal = await item.Author.IsMicrosoftFTE(ospoClient) == true;
- if (teamAuthors.Contains(item.Author?.Login))
- isInternal = true;
- bool isDocIssue = IsDocsIssue(item.Body);
- int ageInMonths = (int)(now - item.CreatedDate).TotalDays / 30;
- var criteria = new CloseCriteria(priority, isDocIssue, isInternal);
- var number = item.Number;
- var title = item.Title;
-
- totalIssues++;
- if (stats[criteria].ShouldCloseIssue(criteria, ageInMonths))
- {
- Console.WriteLine($"Recommend Closing [{number} - {title}]");
- Console.WriteLine($"\t{criteria}, {ageInMonths}");
+ Console.WriteLine($"Recommend Closing [{number} - {title}]");
+ Console.WriteLine($"\t{criteria}, {ageInMonths}");
- totalClosedIssues++;
- if (!dryRun)
- {
- await CloseIssue(client, issueID, labelID);
- Console.WriteLine($"!!!!! Issue CLOSED {number}-{title} !!!!!");
- }
+ totalClosedIssues++;
+ if (!dryRun)
+ {
+ await CloseIssue(client, issueID, labelID);
+ Console.WriteLine($"!!!!! Issue CLOSED {number}-{title} !!!!!");
}
}
-
- foreach (var item in stats.Where(item => item.Value.TotalIssues > 0))
- {
- Console.WriteLine($"- {item.Key}:\n - {item.Value}");
- }
-
- Console.WriteLine($"Closing {totalClosedIssues} of {totalIssues}");
}
- private static async Task CloseIssue(IGitHubClient client, string issueID, string labelID)
+ foreach (var item in stats.Where(item => item.Value.TotalIssues > 0))
{
- // 1. Add label
- Console.WriteLine($"\tAdding [won't fix] label.");
- Console.WriteLine($"\tAdding Closing comment.");
- Console.WriteLine($"\tClosing issue.");
- var closeIssueMutation = new Mutation(client);
- await closeIssueMutation.PerformMutation(new CloseIssueVariables(issueID, labelID, commentText));
+ Console.WriteLine($"- {item.Key}:\n - {item.Value}");
}
- private static async Task> BuildStatsMapAsync()
- {
- using FileStream openStream = File.OpenRead(ConfigFile);
- List? items = await JsonSerializer.DeserializeAsync>(openStream);
- // Uncomment this to build the config file for the first time:
- Dictionary map = new Dictionary();
+ Console.WriteLine($"Closing {totalClosedIssues} of {totalIssues}");
+}
- if (items != null)
+static async Task CloseIssue(IGitHubClient client, string issueID, string labelID)
+{
+ // 1. Add label
+ Console.WriteLine($"\tAdding [won't fix] label.");
+ Console.WriteLine($"\tAdding Closing comment.");
+ Console.WriteLine($"\tClosing issue.");
+ var closeIssueMutation = new Mutation(client);
+ await closeIssueMutation.PerformMutation(new CloseIssueVariables(issueID, labelID, commentText));
+}
+
+static async Task> BuildStatsMapAsync()
+{
+ using FileStream openStream = File.OpenRead(ConfigFile);
+ List? items = await JsonSerializer.DeserializeAsync>(openStream);
+ // Uncomment this to build the config file for the first time:
+ Dictionary map = new Dictionary();
+
+ if (items != null)
+ {
+ foreach (var configItem in items)
{
- foreach (var configItem in items)
- {
- map.Add(configItem.Criteria, new IssueSet { AgeToClose = configItem.AgeInMonths });
- }
+ map.Add(configItem.Criteria, new IssueSet { AgeToClose = configItem.AgeInMonths });
}
- else
+ }
+ else
+ {
+ items = new List();
+ int ageIndex = 0;
+ foreach (var botPriority in System.Enum.GetValues())
{
- items = new List();
- int ageIndex = 0;
- foreach (var botPriority in System.Enum.GetValues())
+ for (int codeBlock = 0; codeBlock < 2; codeBlock++)
{
- for (int codeBlock = 0; codeBlock < 2; codeBlock++)
+ for (int author = 0; author < 2; author++)
{
- for (int author = 0; author < 2; author++)
- {
- var criteria = new CloseCriteria(botPriority, codeBlock == 0, author == 0);
- int age = IssueSet.Ages[ageIndex++];
- items.Add(new(criteria, age));
- map.Add(criteria,
- new IssueSet { AgeToClose = age });
- }
+ var criteria = new CloseCriteria(botPriority, codeBlock == 0, author == 0);
+ int age = IssueSet.Ages[ageIndex++];
+ items.Add(new(criteria, age));
+ map.Add(criteria,
+ new IssueSet { AgeToClose = age });
}
}
-
- using FileStream createStream = File.Create(ConfigFile);
- await JsonSerializer.SerializeAsync(createStream, items, new JsonSerializerOptions() { WriteIndented = true });
}
- return map;
+
+ using FileStream createStream = File.Create(ConfigFile);
+ await JsonSerializer.SerializeAsync(createStream, items, new JsonSerializerOptions() { WriteIndented = true });
}
- private static bool IsDocsIssue(string? body)
+ return map;
+}
+static bool IsDocsIssue(string? body)
+{
+ const string header1 = "---";
+ const string header2 = "#### ";
+ const string header3 = "⚠ *";
+
+ return (body != null) && body.Contains(header1) &&
+ body.Contains(header2) &&
+ body.Contains(header3);
+}
+
+static (string organization, string repository, bool dryRun) ParseArguments(string[] args)
+{
+ Option organizationOption = new("--organization")
+ {
+ Description = "The GitHub organization for the target repository.",
+ DefaultValueFactory = parseResult => "dotnet"
+ };
+ Option repositoryOption = new("--repository")
{
- const string header1 = "---";
- const string header2 = "#### ";
- const string header3 = "⚠ *";
+ Description = "The GitHub target repository.",
+ DefaultValueFactory = parseResult => "docs"
+ };
+ Option dryRunOption = new("--dryRun")
+ {
+ Description = "Flag to specify a dry run (no issues will be closed).",
+ DefaultValueFactory = parseResult => false
+ };
+ RootCommand rootCommand = new("Issue Closer application.");
+
+ rootCommand.Options.Add(organizationOption);
+ rootCommand.Options.Add(repositoryOption);
+ rootCommand.Options.Add(dryRunOption);
- return (body != null) && body.Contains(header1) &&
- body.Contains(header2) &&
- body.Contains(header3);
+ 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 organization = result.GetValue(organizationOption) ?? throw new InvalidOperationException("organization is null");
+ var repository = result.GetValue(repositoryOption) ?? throw new InvalidOperationException("repository is null");
+ var dryRun = result.GetValue(dryRunOption);
+ return (organization, repository, dryRun);
}
diff --git a/actions/sequester/ImportIssues/ImportIssues.csproj b/actions/sequester/ImportIssues/ImportIssues.csproj
index ead02c11..d0f3eb3e 100644
--- a/actions/sequester/ImportIssues/ImportIssues.csproj
+++ b/actions/sequester/ImportIssues/ImportIssues.csproj
@@ -1,4 +1,4 @@
-
+
Exe
net10.0
@@ -15,8 +15,7 @@
-
-
+
diff --git a/actions/sequester/ImportIssues/Program.cs b/actions/sequester/ImportIssues/Program.cs
index 252ba04b..2a582934 100644
--- a/actions/sequester/ImportIssues/Program.cs
+++ b/actions/sequester/ImportIssues/Program.cs
@@ -1,116 +1,150 @@
-using DotNetDocs.Tools.GitHubCommunications;
+using System.CommandLine;
+using System.CommandLine.Parsing;
+using DotNetDocs.Tools.GitHubCommunications;
using Microsoft.DotnetOrg.Ospo;
-internal class Program
+(string org, string repo, int? issue, int? duration, string? questConfigPath, string? branch) = ParseArguments(args);
+
+try
{
- ///
- /// Process issue updates in Azure DevOps - Quest
- ///
- /// The GitHub organization
- /// The GutHub repository names.
- /// The issue number. If null, process all open issues.
- /// The config path. If null, use the config file in the root folder of the repository.
- /// The optional branch to use. Defaults to "main" otherwise.
- /// For bulk import, how many days past to examine. -1 means all issues. Default is 5
- ///
- /// Example command line:
- /// ImportIssues --org dotnet --repo docs --issue 31331
- ///
- /// 0
- private static async Task Main(
- string org,
- string repo,
- int? issue = null,
- int? duration = null,
- string? questConfigPath = null,
- string? branch = null)
+ if (repo.Contains('/'))
+ {
+ string[] split = repo.Split("/");
+ repo = split[1];
+ }
+
+ branch ??= "main";
+ Console.WriteLine($"Using branch: '{branch}'");
+
+ bool singleIssue = (issue is not null && issue.Value != -1);
+
+ Console.WriteLine(singleIssue
+ ? $"Processing single issue {issue!.Value}: https://github.com/{org}/{repo}/issues/{issue.Value}"
+ : (duration is not null) && (duration != -1)
+ ? $"Processing all issues updated in the last {duration} days: {org}/{repo}"
+ : $"Processing all open issues: {org}/{repo}");
+
+ ImportOptions? importOptions;
+ if (string.IsNullOrWhiteSpace(questConfigPath) || !File.Exists(questConfigPath))
{
- try
- {
- if (repo.Contains('/'))
- {
- string[] split = repo.Split("/");
- repo = split[1];
- }
-
- branch ??= "main";
- Console.WriteLine($"Using branch: '{branch}'");
-
- bool singleIssue = (issue is not null && issue.Value != -1);
-
- Console.WriteLine(singleIssue
- ? $"Processing single issue {issue!.Value}: https://github.com/{org}/{repo}/issues/{issue.Value}"
- : (duration is not null) && (duration != -1)
- ? $"Processing all issues updated in the last {duration} days: {org}/{repo}"
- : $"Processing all open issues: {org}/{repo}");
-
- ImportOptions? importOptions;
- if (string.IsNullOrWhiteSpace(questConfigPath) || !File.Exists(questConfigPath))
- {
- using RawGitHubFileReader reader = new();
- importOptions = await reader.ReadOptionsAsync(org, repo, branch);
- }
- else
- {
- LocalFileReader reader = new();
- importOptions = await reader.ReadOptionsAsync(questConfigPath);
- }
-
- if (importOptions is null)
- {
- throw new ApplicationException(
- $"Unable to load Quest import configuration options.");
- }
-
- using QuestGitHubService serviceWorker = await CreateService(importOptions, !singleIssue);
-
- if (singleIssue)
- {
- await serviceWorker.ProcessIssue(
- org, repo, issue!.Value); // Odd. There's a warning on issue, but it is null checked above.
- }
- else
- {
- await serviceWorker.ProcessIssues(
- org, repo, duration ?? -1);
- }
- }
- catch (InvalidOperationException e) when (e.Message.StartsWith("HTTP error:"))
- {
- Console.Error.WriteLine($"!!!ERROR!!! Could not communicate with Quest Azure DevOps server. Did your PAT expire?");
- Console.Error.WriteLine($":: -- {e.Message} -- ");
- Console.Error.WriteLine(e.ToString());
- return 1;
- }
- catch (Exception ex)
- {
- Console.Error.WriteLine($":: -- {ex.Message} -- ");
- Console.Error.WriteLine(ex.ToString());
- return 1;
- }
- return 0;
+ using RawGitHubFileReader reader = new();
+ importOptions = await reader.ReadOptionsAsync(org, repo, branch);
+ }
+ else
+ {
+ LocalFileReader reader = new();
+ importOptions = await reader.ReadOptionsAsync(questConfigPath);
}
- private static async Task CreateService(ImportOptions options, bool bulkImport)
+ if (importOptions is null)
{
- ArgumentNullException.ThrowIfNull(options.ApiKeys, nameof(options));
+ throw new ApplicationException(
+ $"Unable to load Quest import configuration options.");
+ }
- IGitHubClient gitHubClient = (options.ApiKeys.SequesterAppID != 0)
- ? await IGitHubClient.CreateGitHubAppClient(options.ApiKeys.SequesterAppID, options.ApiKeys.SequesterPrivateKey)
- : IGitHubClient.CreateGitHubClient(options.ApiKeys.GitHubToken);
+ using QuestGitHubService serviceWorker = await CreateService(importOptions, !singleIssue);
- var ospoClient = (options.ApiKeys.AzureAccessToken is not null)
- ? new OspoClient(options.ApiKeys.AzureAccessToken, false)
- : null;
+ if (singleIssue)
+ {
+ await serviceWorker.ProcessIssue(
+ org, repo, issue!.Value); // Odd. There's a warning on issue, but it is null checked above.
+ }
+ else
+ {
+ await serviceWorker.ProcessIssues(
+ org, repo, duration ?? -1);
+ }
+}
+catch (InvalidOperationException e) when (e.Message.StartsWith("HTTP error:"))
+{
+ Console.Error.WriteLine($"!!!ERROR!!! Could not communicate with Quest Azure DevOps server. Did your PAT expire?");
+ Console.Error.WriteLine($":: -- {e.Message} -- ");
+ Console.Error.WriteLine(e.ToString());
+ return 1;
+}
+catch (Exception ex)
+{
+ Console.Error.WriteLine($":: -- {ex.Message} -- ");
+ Console.Error.WriteLine(ex.ToString());
+ return 1;
+}
+return 0;
- if (ospoClient is null)
- {
- Console.WriteLine("Warning: Imported work items won't be assigned based on GitHub assignee.");
- }
+static async Task CreateService(ImportOptions options, bool bulkImport)
+{
+ ArgumentNullException.ThrowIfNull(options.ApiKeys, nameof(options));
- return new QuestGitHubService(
- gitHubClient,
- ospoClient,
- options);
+ IGitHubClient gitHubClient = (options.ApiKeys.SequesterAppID != 0)
+ ? await IGitHubClient.CreateGitHubAppClient(options.ApiKeys.SequesterAppID, options.ApiKeys.SequesterPrivateKey)
+ : IGitHubClient.CreateGitHubClient(options.ApiKeys.GitHubToken);
+
+ var ospoClient = (options.ApiKeys.AzureAccessToken is not null)
+ ? new OspoClient(options.ApiKeys.AzureAccessToken, false)
+ : null;
+
+ if (ospoClient is null)
+ {
+ Console.WriteLine("Warning: Imported work items won't be assigned based on GitHub assignee.");
+ }
+
+ return new QuestGitHubService(
+ gitHubClient,
+ ospoClient,
+ options);
+}
+
+static (string org, string repo, int? issue, int? duration, string? questConfigPath, string? branch) ParseArguments(string[] args)
+{
+ Option orgOption = new("--org")
+ {
+ Description = "The GitHub organization."
+ };
+ Option repoOption = new("--repo")
+ {
+ Description = "The GitHub target repository.",
+ };
+ Option issueOption = new("--issue")
+ {
+ Description = "The issue number. If null, process all changed issues.",
+ DefaultValueFactory = parseResult => null
+ };
+ Option durationOption = new("--duration")
+ {
+ Description = "For bulk import, how many days past to examine. -1 means all open issues. Default is 5",
+ DefaultValueFactory = parseResult => null
+ };
+ Option questConfigPathOption = new("--questConfigPath")
+ {
+ Description = "The config path. If null, use the config file in the root folder of the repository.",
+ };
+ Option branchOption = new("--branch")
+ {
+ Description = "The optional branch to use. Defaults to \"main\" otherwise.",
+ DefaultValueFactory = parseResult => "main"
+ };
+ RootCommand rootCommand = new("Sequester AzureDevOps import application");
+
+ rootCommand.Options.Add(orgOption);
+ rootCommand.Options.Add(repoOption);
+ rootCommand.Options.Add(issueOption);
+ rootCommand.Options.Add(durationOption);
+ rootCommand.Options.Add(questConfigPathOption);
+ rootCommand.Options.Add(branchOption);
+
+ 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 org = result.GetValue(orgOption) ?? throw new InvalidOperationException("organization is null");
+ var repo = result.GetValue(repoOption) ?? throw new InvalidOperationException("repository is null");
+ var issue = result.GetValue(issueOption);
+ var duration = result.GetValue(durationOption);
+ var questConfigPath = result.GetValue(questConfigPathOption);
+ var branch = result.GetValue(branchOption);
+ return (org, repo, issue, duration, questConfigPath, branch);
}
diff --git a/snippets5000/Snippets5000/Program.cs b/snippets5000/Snippets5000/Program.cs
index 7142d72d..327ed698 100644
--- a/snippets5000/Snippets5000/Program.cs
+++ b/snippets5000/Snippets5000/Program.cs
@@ -1,10 +1,12 @@
-using DotNetDocs.Tools.Utility;
+using System.CommandLine;
+using System.CommandLine.Parsing;
using System.Diagnostics;
-using System.Text.RegularExpressions;
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;
-using DotNet.DocsTools.Utility;
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("PullRequestSimulations")]
@@ -38,15 +40,11 @@ class Program
///
/// LocateProjects: Find all projects and solutions requiring a build.
///
- /// The directory containing the local source tree.
- /// If available, the number of the pull request being built.
- /// If available, the owner organization of the repository.
- /// If available, the name of the repository.
- /// The test id from data.json to simulate a pull request.
- /// The json file defining all the tests that can be referenced by . Usually data.json.
/// 0 on success. Otherwise, a non-zero error code.
- static async Task Main(string sourcepath, int? pullrequest = default, string? owner=default, string? repo=default, string? dryrunTestId=default, string? dryrunTestDataFile=default)
+ static async Task Main(string[] args)
{
+ var (sourcepath, pullrequest, owner, repo, dryrunTestId, dryrunTestDataFile) = ParseArguments(args);
+
int exitCode = EXITCODE_GOOD;
string appStartupFolder = Directory.GetCurrentDirectory();
@@ -70,7 +68,7 @@ static async Task Main(string sourcepath, int? pullrequest = default, strin
// NOT a normal github PR and instead is a test
else if (string.IsNullOrEmpty(dryrunTestDataFile))
- throw new ArgumentNullException(nameof(dryrunTestDataFile), "The dryrun Test DataFile must be set");
+ throw new InvalidOperationException($"{nameof(dryrunTestDataFile)}: The dryrun Test DataFile must be set");
else
projects = new TestingProjectList(dryrunTestId, dryrunTestDataFile, sourcepath).GenerateBuildList().ToList();
@@ -459,4 +457,62 @@ 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);
+ }
}
diff --git a/snippets5000/Snippets5000/Snippets5000.csproj b/snippets5000/Snippets5000/Snippets5000.csproj
index d7a8a9a1..f2079f50 100644
--- a/snippets5000/Snippets5000/Snippets5000.csproj
+++ b/snippets5000/Snippets5000/Snippets5000.csproj
@@ -11,7 +11,7 @@
$(DefineConstants);LINUX
-
+