From 1e0c55560d19a4ea8edcad2b3260d80e6df3201c Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 5 Sep 2025 17:52:01 +1000 Subject: [PATCH 01/11] Improving logging and status display --- .../Commands/Helpers/InputProviderExtensionMethods.cs | 5 ----- src/Stack/Commands/Helpers/StackActions.cs | 4 +++- src/Stack/Commands/Remote/SyncStackCommand.cs | 6 +++--- src/Stack/Git/GitClient.cs | 11 ++++++++++- src/Stack/Infrastructure/CommonOptions.cs | 4 ++-- .../HostApplicationBuilderExtensions.cs | 4 ++-- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs b/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs index 2317b728..2ea37ab6 100644 --- a/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs +++ b/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs @@ -64,11 +64,6 @@ public static async Task MultiSelect( string currentBranch, CancellationToken cancellationToken) { - if (stacks.Count == 0) - { - return null; - } - if (name is not null) { return stacks.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); diff --git a/src/Stack/Commands/Helpers/StackActions.cs b/src/Stack/Commands/Helpers/StackActions.cs index 01d4fbe3..4a834983 100644 --- a/src/Stack/Commands/Helpers/StackActions.cs +++ b/src/Stack/Commands/Helpers/StackActions.cs @@ -302,7 +302,7 @@ private async Task UpdateBranchLineUsingRebase(StackStatus status, List + await displayProvider.DisplayStatus($"Rebasing {branch} onto {sourceBranchName}", async ct => { gitClient.ChangeBranch(branch); @@ -333,6 +333,8 @@ await displayProvider.DisplayStatusWithSuccess($"Rebasing {branch} onto {sourceB break; } } + + await displayProvider.DisplayMessage($"{Emoji.Known.CheckMark} Rebasing {branch} onto {sourceBranchName}...", ct); }, cancellationToken); } diff --git a/src/Stack/Commands/Remote/SyncStackCommand.cs b/src/Stack/Commands/Remote/SyncStackCommand.cs index 5c2d2df3..51f77f00 100644 --- a/src/Stack/Commands/Remote/SyncStackCommand.cs +++ b/src/Stack/Commands/Remote/SyncStackCommand.cs @@ -95,7 +95,7 @@ public override async Task Handle(SyncStackCommandInputs inputs, CancellationTok if (stack is null) throw new InvalidOperationException($"Stack '{inputs.Stack}' not found."); - await displayProvider.DisplayStatusWithSuccess("Fetching changes from remote repository...", async (ct) => + await displayProvider.DisplayStatus("Fetching changes from remote repository...", async (ct) => { await Task.CompletedTask; gitClient.Fetch(true); @@ -125,7 +125,7 @@ await displayProvider.DisplayStatus("Checking stack status...", async (ct) => } } - await displayProvider.DisplayStatusWithSuccess("Pulling changes from remote repository...", async (ct) => + await displayProvider.DisplayStatus("Pulling changes from remote repository...", async (ct) => { await Task.CompletedTask; stackActions.PullChanges(stack); @@ -144,7 +144,7 @@ await displayProvider.DisplayStatus("Updating stack...", async (ct) => if (!inputs.NoPush) { - await displayProvider.DisplayStatusWithSuccess("Pushing changes to remote repository...", async (ct) => + await displayProvider.DisplayStatus("Pushing changes to remote repository...", async (ct) => { await Task.CompletedTask; stackActions.PushChanges(stack, inputs.MaxBatchSize, forceWithLease); diff --git a/src/Stack/Git/GitClient.cs b/src/Stack/Git/GitClient.cs index bd5bf701..c99b8d9a 100644 --- a/src/Stack/Git/GitClient.cs +++ b/src/Stack/Git/GitClient.cs @@ -332,7 +332,16 @@ private void ExecuteGitCommand( bool captureStandardError = false, Func? exceptionHandler = null) { - ExecuteGitCommandAndReturnOutput(command, captureStandardError, exceptionHandler); + var output = ExecuteGitCommandAndReturnOutput(command, captureStandardError, exceptionHandler); + + // if (output.Length > 0) + // { + // // We want to write the output of commands that perform + // // changes to the Git repository as the output might be important. + // // In verbose mode we would have already written the output + // // of the command so don't write it again. + // logger.DebugCommandOutput(Markup.Escape(output)); + // } } } diff --git a/src/Stack/Infrastructure/CommonOptions.cs b/src/Stack/Infrastructure/CommonOptions.cs index 59339976..b959fd09 100644 --- a/src/Stack/Infrastructure/CommonOptions.cs +++ b/src/Stack/Infrastructure/CommonOptions.cs @@ -8,13 +8,13 @@ public static class CommonOptions Required = false }; - public static Option Debug { get; } = new Option("--debug") + public static Option Debug { get; } = new Option("--debug", "-d") { Description = "Show debug output.", Required = false }; - public static Option Verbose { get; } = new Option("--verbose") + public static Option Verbose { get; } = new Option("--verbose", "-v") { Description = "Show verbose output.", Required = false diff --git a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs index b531d25e..05dbba14 100644 --- a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs +++ b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs @@ -24,11 +24,11 @@ public static IHostApplicationBuilder ConfigureLogging(this IHostApplicationBuil { builder.Logging.SetMinimumLevel(LogLevel.Information); - if (args.Contains(CommonOptions.Debug.Name) || args.Any(CommonOptions.Debug.Aliases.Contains)) + if (args.Contains(CommonOptions.Debug.Name) || args.Any(a => CommonOptions.Debug.Aliases.Contains(a))) { builder.Logging.SetMinimumLevel(LogLevel.Debug); } - else if (args.Contains(CommonOptions.Verbose.Name) || args.Any(CommonOptions.Verbose.Aliases.Contains)) + else if (args.Contains(CommonOptions.Verbose.Name) || args.Any(a => CommonOptions.Verbose.Aliases.Contains(a))) { builder.Logging.SetMinimumLevel(LogLevel.Trace); } From 7fd4163d0a67177762170233a3d8f5ad27fa9fc1 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 12 Sep 2025 08:39:56 +1000 Subject: [PATCH 02/11] Experimenting with success messages --- src/Stack/Commands/Helpers/StackActions.cs | 4 +--- src/Stack/Commands/Remote/SyncStackCommand.cs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Stack/Commands/Helpers/StackActions.cs b/src/Stack/Commands/Helpers/StackActions.cs index 4a834983..01d4fbe3 100644 --- a/src/Stack/Commands/Helpers/StackActions.cs +++ b/src/Stack/Commands/Helpers/StackActions.cs @@ -302,7 +302,7 @@ private async Task UpdateBranchLineUsingRebase(StackStatus status, List + await displayProvider.DisplayStatusWithSuccess($"Rebasing {branch} onto {sourceBranchName}", async ct => { gitClient.ChangeBranch(branch); @@ -333,8 +333,6 @@ await displayProvider.DisplayStatus($"Rebasing {branch} onto {sourceBranchName}" break; } } - - await displayProvider.DisplayMessage($"{Emoji.Known.CheckMark} Rebasing {branch} onto {sourceBranchName}...", ct); }, cancellationToken); } diff --git a/src/Stack/Commands/Remote/SyncStackCommand.cs b/src/Stack/Commands/Remote/SyncStackCommand.cs index 51f77f00..5c2d2df3 100644 --- a/src/Stack/Commands/Remote/SyncStackCommand.cs +++ b/src/Stack/Commands/Remote/SyncStackCommand.cs @@ -95,7 +95,7 @@ public override async Task Handle(SyncStackCommandInputs inputs, CancellationTok if (stack is null) throw new InvalidOperationException($"Stack '{inputs.Stack}' not found."); - await displayProvider.DisplayStatus("Fetching changes from remote repository...", async (ct) => + await displayProvider.DisplayStatusWithSuccess("Fetching changes from remote repository...", async (ct) => { await Task.CompletedTask; gitClient.Fetch(true); @@ -125,7 +125,7 @@ await displayProvider.DisplayStatus("Checking stack status...", async (ct) => } } - await displayProvider.DisplayStatus("Pulling changes from remote repository...", async (ct) => + await displayProvider.DisplayStatusWithSuccess("Pulling changes from remote repository...", async (ct) => { await Task.CompletedTask; stackActions.PullChanges(stack); @@ -144,7 +144,7 @@ await displayProvider.DisplayStatus("Updating stack...", async (ct) => if (!inputs.NoPush) { - await displayProvider.DisplayStatus("Pushing changes to remote repository...", async (ct) => + await displayProvider.DisplayStatusWithSuccess("Pushing changes to remote repository...", async (ct) => { await Task.CompletedTask; stackActions.PushChanges(stack, inputs.MaxBatchSize, forceWithLease); From b9a747d3fe66fb3ceb7b53b3048674dce504469e Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 12 Sep 2025 17:26:39 +1000 Subject: [PATCH 03/11] Cleanup --- src/Stack/Git/GitClient.cs | 11 +---------- src/Stack/Infrastructure/CommonOptions.cs | 4 ++-- .../HostApplicationBuilderExtensions.cs | 4 ++-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Stack/Git/GitClient.cs b/src/Stack/Git/GitClient.cs index c99b8d9a..bd5bf701 100644 --- a/src/Stack/Git/GitClient.cs +++ b/src/Stack/Git/GitClient.cs @@ -332,16 +332,7 @@ private void ExecuteGitCommand( bool captureStandardError = false, Func? exceptionHandler = null) { - var output = ExecuteGitCommandAndReturnOutput(command, captureStandardError, exceptionHandler); - - // if (output.Length > 0) - // { - // // We want to write the output of commands that perform - // // changes to the Git repository as the output might be important. - // // In verbose mode we would have already written the output - // // of the command so don't write it again. - // logger.DebugCommandOutput(Markup.Escape(output)); - // } + ExecuteGitCommandAndReturnOutput(command, captureStandardError, exceptionHandler); } } diff --git a/src/Stack/Infrastructure/CommonOptions.cs b/src/Stack/Infrastructure/CommonOptions.cs index b959fd09..59339976 100644 --- a/src/Stack/Infrastructure/CommonOptions.cs +++ b/src/Stack/Infrastructure/CommonOptions.cs @@ -8,13 +8,13 @@ public static class CommonOptions Required = false }; - public static Option Debug { get; } = new Option("--debug", "-d") + public static Option Debug { get; } = new Option("--debug") { Description = "Show debug output.", Required = false }; - public static Option Verbose { get; } = new Option("--verbose", "-v") + public static Option Verbose { get; } = new Option("--verbose") { Description = "Show verbose output.", Required = false diff --git a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs index 05dbba14..b531d25e 100644 --- a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs +++ b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs @@ -24,11 +24,11 @@ public static IHostApplicationBuilder ConfigureLogging(this IHostApplicationBuil { builder.Logging.SetMinimumLevel(LogLevel.Information); - if (args.Contains(CommonOptions.Debug.Name) || args.Any(a => CommonOptions.Debug.Aliases.Contains(a))) + if (args.Contains(CommonOptions.Debug.Name) || args.Any(CommonOptions.Debug.Aliases.Contains)) { builder.Logging.SetMinimumLevel(LogLevel.Debug); } - else if (args.Contains(CommonOptions.Verbose.Name) || args.Any(a => CommonOptions.Verbose.Aliases.Contains(a))) + else if (args.Contains(CommonOptions.Verbose.Name) || args.Any(CommonOptions.Verbose.Aliases.Contains)) { builder.Logging.SetMinimumLevel(LogLevel.Trace); } From de77301b8589aa25060c6a9846912c8fc1b02b08 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Wed, 17 Sep 2025 08:29:10 +1000 Subject: [PATCH 04/11] Improve handling of no stacks in repo --- src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs b/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs index 2ea37ab6..2317b728 100644 --- a/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs +++ b/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs @@ -64,6 +64,11 @@ public static async Task MultiSelect( string currentBranch, CancellationToken cancellationToken) { + if (stacks.Count == 0) + { + return null; + } + if (name is not null) { return stacks.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); From dad9b00102477713ee9fa77de5f40c655b42f629 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 12 Sep 2025 17:59:47 +1000 Subject: [PATCH 05/11] wip adding Json logging provider --- global.json | 6 + src/Stack/Commands/Helpers/StackHelpers.cs | 57 ++++--- .../PullRequests/CreatePullRequestsCommand.cs | 21 ++- src/Stack/Commands/Remote/SyncStackCommand.cs | 5 +- src/Stack/Commands/Stack/ListStacksCommand.cs | 5 +- .../Commands/Stack/StackStatusCommand.cs | 9 +- src/Stack/Infrastructure/Commands/Command.cs | 1 + .../Infrastructure/ConsoleDisplayProvider.cs | 157 ++++++++++++++++-- .../HostApplicationBuilderExtensions.cs | 21 ++- 9 files changed, 227 insertions(+), 55 deletions(-) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 00000000..ff07225f --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "9.0.203", + "rollForward": "latestFeature" + } +} diff --git a/src/Stack/Commands/Helpers/StackHelpers.cs b/src/Stack/Commands/Helpers/StackHelpers.cs index 08b36d9a..56a3f85f 100644 --- a/src/Stack/Commands/Helpers/StackHelpers.cs +++ b/src/Stack/Commands/Helpers/StackHelpers.cs @@ -237,18 +237,19 @@ public static StackStatus GetStackStatus( public static async Task OutputStackStatus( List statuses, + IOutputProvider outputProvider, IDisplayProvider displayProvider, CancellationToken cancellationToken) { foreach (var status in statuses) { - await OutputStackStatus(status, displayProvider, cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await OutputStackStatus(status, outputProvider, displayProvider, cancellationToken); } } public static async Task OutputStackStatus( StackStatus status, + IOutputProvider outputProvider, IDisplayProvider displayProvider, CancellationToken cancellationToken, Func? getBranchPullRequestDisplay = null) @@ -261,8 +262,10 @@ public static async Task OutputStackStatus( items.Add(GetBranchAndPullRequestStatusOutput(branch, getBranchPullRequestDisplay)); } - await displayProvider.DisplayMessage(status.Name.Stack(), cancellationToken); - await displayProvider.DisplayTree(header, [.. items], cancellationToken: cancellationToken); + var tree = RenderingHelpers.RenderTree(header, items); + + await outputProvider.WriteMessage(status.Name.Stack(), cancellationToken); + await outputProvider.WriteLine(tree, cancellationToken); } public static TreeItem GetBranchAndPullRequestStatusOutput( @@ -427,48 +430,48 @@ public static string GetBranchStatusOutput(BranchDetail branch) public static async Task OutputBranchAndStackActions( StackStatus status, - IDisplayProvider displayProvider, + IOutputProvider outputProvider, CancellationToken cancellationToken) { var allBranches = status.GetAllBranches(); if (allBranches.All(branch => branch.CouldBeCleanedUp)) { - await displayProvider.DisplayMessage("All branches exist locally but are either not in the remote repository or the pull request associated with the branch is no longer open. This stack might be able to be deleted.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); - await displayProvider.DisplayMessage($"Run {$"stack delete --stack \"{status.Name}\"".Example()} to delete the stack if it's no longer needed.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteMessage("All branches exist locally but are either not in the remote repository or the pull request associated with the branch is no longer open. This stack might be able to be deleted.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); + await outputProvider.WriteMessage($"Run {$"stack delete --stack \"{status.Name}\"".Example()} to delete the stack if it's no longer needed.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); } else if (allBranches.Any(branch => branch.CouldBeCleanedUp)) { - await displayProvider.DisplayMessage("Some branches exist locally but are either not in the remote repository or the pull request associated with the branch is no longer open.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); - await displayProvider.DisplayMessage($"Run {$"stack cleanup --stack \"{status.Name}\"".Example()} to clean up the stack if it's no longer needed.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteMessage("Some branches exist locally but are either not in the remote repository or the pull request associated with the branch is no longer open.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); + await outputProvider.WriteMessage($"Run {$"stack cleanup --stack \"{status.Name}\"".Example()} to clean up the stack if it's no longer needed.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); } else if (allBranches.All(branch => !branch.Exists)) { - await displayProvider.DisplayMessage("No branches exist locally. This stack might be able to be deleted.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); - await displayProvider.DisplayMessage($"Run {$"stack delete --stack \"{status.Name}\"".Example()} to delete the stack if it's no longer needed.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteMessage("No branches exist locally. This stack might be able to be deleted.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); + await outputProvider.WriteMessage($"Run {$"stack delete --stack \"{status.Name}\"".Example()} to delete the stack if it's no longer needed.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); } if (allBranches.Any(branch => branch.Exists && (branch.RemoteTrackingBranch is null || branch.RemoteTrackingBranch.Ahead > 0))) { - await displayProvider.DisplayMessage("There are changes in local branches that have not been pushed to the remote repository.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); - await displayProvider.DisplayMessage($"Run {$"stack push --stack \"{status.Name}\"".Example()} to push the changes to the remote repository.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteMessage("There are changes in local branches that have not been pushed to the remote repository.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); + await outputProvider.WriteMessage($"Run {$"stack push --stack \"{status.Name}\"".Example()} to push the changes to the remote repository.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); } if (allBranches.Any(branch => branch.Exists && branch.RemoteTrackingBranch is not null && branch.RemoteTrackingBranch.Behind > 0)) { - await displayProvider.DisplayMessage("There are changes in source branches that have not been applied to the stack.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); - await displayProvider.DisplayMessage($"Run {$"stack update --stack \"{status.Name}\"".Example()} to update the stack locally.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); - await displayProvider.DisplayMessage($"Run {$"stack sync --stack \"{status.Name}\"".Example()} to sync the stack with the remote repository.", cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteMessage("There are changes in source branches that have not been applied to the stack.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); + await outputProvider.WriteMessage($"Run {$"stack update --stack \"{status.Name}\"".Example()} to update the stack locally.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); + await outputProvider.WriteMessage($"Run {$"stack sync --stack \"{status.Name}\"".Example()} to sync the stack with the remote repository.", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); } } diff --git a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs index 0ef2d55e..7791f3b9 100644 --- a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs +++ b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs @@ -43,6 +43,7 @@ public record CreatePullRequestsCommandInputs(string? Stack) public class CreatePullRequestsCommandHandler( IInputProvider inputProvider, ILogger logger, + IOutputProvider outputProvider, IDisplayProvider displayProvider, IGitClient gitClient, IGitHubClient gitHubClient, @@ -104,9 +105,9 @@ public override async Task Handle(CreatePullRequestsCommandInputs inputs, Cancel } } - await StackHelpers.OutputStackStatus(status, displayProvider, cancellationToken); + await StackHelpers.OutputStackStatus(status, outputProvider, displayProvider, cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); if (pullRequestCreateActions.Count > 0) { @@ -124,22 +125,23 @@ public override async Task Handle(CreatePullRequestsCommandInputs inputs, Cancel logger.PullRequestSelected(action.Branch, action.BaseBranch); } - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); var pullRequestInformation = await GetPullRequestInformation( inputProvider, logger, + outputProvider, displayProvider, gitClient, fileOperations, selectedPullRequestActions, cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); - await OutputUpdatedStackStatus(logger, displayProvider, stack, status, pullRequestInformation, cancellationToken); + await OutputUpdatedStackStatus(logger, outputProvider, displayProvider, stack, status, pullRequestInformation, cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); if (await inputProvider.Confirm(Questions.ConfirmCreatePullRequests, cancellationToken)) { @@ -200,6 +202,7 @@ private static List CreatePullRequests( private static async Task OutputUpdatedStackStatus( ILogger logger, + IOutputProvider outputProvider, IDisplayProvider displayProvider, Config.Stack stack, StackStatus status, @@ -208,6 +211,7 @@ private static async Task OutputUpdatedStackStatus( { await StackHelpers.OutputStackStatus( status, + outputProvider, displayProvider, cancellationToken, (branch) => @@ -226,6 +230,7 @@ await StackHelpers.OutputStackStatus( private static async Task> GetPullRequestInformation( IInputProvider inputProvider, ILogger logger, + IOutputProvider outputProvider, IDisplayProvider displayProvider, IGitClient gitClient, IFileOperations fileOperations, @@ -246,8 +251,8 @@ private static async Task> GetPullRequestInformatio foreach (var action in pullRequestCreateActions) { - await displayProvider.DisplayNewLine(cancellationToken); - await displayProvider.DisplayHeader($"New pull request from {action.Branch.Branch()} -> {action.BaseBranch.Branch()}", cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); + await outputProvider.WriteHeader($"New pull request from {action.Branch.Branch()} -> {action.BaseBranch.Branch()}", cancellationToken); var title = inputProvider.Text(Questions.PullRequestTitle, cancellationToken).Result; var bodyFilePath = Path.Join(fileOperations.GetTempPath(), $"stack-pr-{Guid.NewGuid():N}.md"); diff --git a/src/Stack/Commands/Remote/SyncStackCommand.cs b/src/Stack/Commands/Remote/SyncStackCommand.cs index 5c2d2df3..ae26f7a9 100644 --- a/src/Stack/Commands/Remote/SyncStackCommand.cs +++ b/src/Stack/Commands/Remote/SyncStackCommand.cs @@ -63,6 +63,7 @@ public record SyncStackCommandInputs( public class SyncStackCommandHandler( IInputProvider inputProvider, ILogger logger, + IOutputProvider outputProvider, IDisplayProvider displayProvider, IGitClient gitClient, IGitHubClient gitHubClient, @@ -114,10 +115,10 @@ await displayProvider.DisplayStatus("Checking stack status...", async (ct) => gitHubClient, true); - await StackHelpers.OutputStackStatus(status, displayProvider, cancellationToken); + await StackHelpers.OutputStackStatus(status, outputProvider, displayProvider, cancellationToken); }, cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await outputProvider.WriteNewLine(cancellationToken); if (!await inputProvider.Confirm(Questions.ConfirmSyncStack, cancellationToken)) { diff --git a/src/Stack/Commands/Stack/ListStacksCommand.cs b/src/Stack/Commands/Stack/ListStacksCommand.cs index a6a47cc8..c5e4a687 100644 --- a/src/Stack/Commands/Stack/ListStacksCommand.cs +++ b/src/Stack/Commands/Stack/ListStacksCommand.cs @@ -20,9 +20,11 @@ internal partial class ListStacksCommandJsonSerializerContext : JsonSerializerCo public class ListStacksCommand : CommandWithOutput { private readonly ListStacksCommandHandler handler; + private readonly IOutputProvider outputProvider; public ListStacksCommand( ILogger logger, + IOutputProvider outputProvider, IDisplayProvider displayProvider, IInputProvider inputProvider, CliExecutionContext executionContext, @@ -30,6 +32,7 @@ public ListStacksCommand( : base("list", "List stacks.", logger, displayProvider, inputProvider, executionContext) { this.handler = handler; + this.outputProvider = outputProvider; } protected override async Task ExecuteAndReturnResponse(ParseResult parseResult, CancellationToken cancellationToken) @@ -47,7 +50,7 @@ protected override async Task WriteDefaultOutput(ListStacksCommandResponse respo foreach (var stack in response.Stacks) { - await DisplayProvider.DisplayMessage($"{stack.Name.Stack()} {$"({stack.SourceBranch})".Muted()} {stack.BranchCount} {(stack.BranchCount == 1 ? "branch" : "branches")}", cancellationToken); + await outputProvider.WriteMessage($"{stack.Name.Stack()} {$"({stack.SourceBranch})".Muted()} {stack.BranchCount} {(stack.BranchCount == 1 ? "branch" : "branches")}", cancellationToken); } } diff --git a/src/Stack/Commands/Stack/StackStatusCommand.cs b/src/Stack/Commands/Stack/StackStatusCommand.cs index b8f4cef0..1909b333 100644 --- a/src/Stack/Commands/Stack/StackStatusCommand.cs +++ b/src/Stack/Commands/Stack/StackStatusCommand.cs @@ -94,9 +94,11 @@ public class StackStatusCommand : CommandWithOutput }; private readonly StackStatusCommandHandler handler; + private readonly IOutputProvider outputProvider; public StackStatusCommand( ILogger logger, + IOutputProvider outputProvider, IDisplayProvider displayProvider, IInputProvider inputProvider, CliExecutionContext executionContext, @@ -104,6 +106,7 @@ public StackStatusCommand( : base("status", "Show the status of the current stack or all stacks in the repository.", logger, displayProvider, inputProvider, executionContext) { this.handler = handler; + this.outputProvider = outputProvider; Add(CommonOptions.Stack); Add(All); Add(Full); @@ -121,12 +124,12 @@ protected override async Task ExecuteAndReturnRespon protected override async Task WriteDefaultOutput(StackStatusCommandResponse response, CancellationToken cancellationToken) { - await StackHelpers.OutputStackStatus(response.Stacks, DisplayProvider, cancellationToken); + await StackHelpers.OutputStackStatus(response.Stacks, outputProvider, DisplayProvider, cancellationToken); if (response.Stacks.Count == 1) { var stack = response.Stacks.First(); - await StackHelpers.OutputBranchAndStackActions(stack, DisplayProvider, cancellationToken); + await StackHelpers.OutputBranchAndStackActions(stack, outputProvider, cancellationToken); } } @@ -134,7 +137,7 @@ protected override async Task WriteJsonOutput(StackStatusCommandResponse respons { var output = response.Stacks.Select(MapToJsonOutput).ToList(); var json = JsonSerializer.Serialize(output, typeof(List), StackStatusCommandJsonSerializerContext.Default); - await StdOut.WriteLineAsync(json.AsMemory(), cancellationToken); + await outputProvider.WriteLine(json, cancellationToken); } private static StackStatusCommandJsonOutput MapToJsonOutput(StackStatus stack) diff --git a/src/Stack/Infrastructure/Commands/Command.cs b/src/Stack/Infrastructure/Commands/Command.cs index 801cd5d0..d2d0f0c7 100644 --- a/src/Stack/Infrastructure/Commands/Command.cs +++ b/src/Stack/Infrastructure/Commands/Command.cs @@ -30,6 +30,7 @@ public Command( Add(CommonOptions.WorkingDirectory); Add(CommonOptions.Debug); Add(CommonOptions.Verbose); + Add(CommonOptions.Json); SetAction(async (parseResult, cancellationToken) => { diff --git a/src/Stack/Infrastructure/ConsoleDisplayProvider.cs b/src/Stack/Infrastructure/ConsoleDisplayProvider.cs index 10a5026e..c7eeb4e8 100644 --- a/src/Stack/Infrastructure/ConsoleDisplayProvider.cs +++ b/src/Stack/Infrastructure/ConsoleDisplayProvider.cs @@ -8,10 +8,7 @@ public interface IDisplayProvider { Task DisplayStatus(string message, Func action, CancellationToken cancellationToken = default); Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default); - Task DisplayTree(string header, IEnumerable> items, Func? itemFormatter = null, CancellationToken cancellationToken = default) where T : notnull; - Task DisplayMessage(string message, CancellationToken cancellationToken = default); - Task DisplaySuccess(string message, CancellationToken cancellationToken = default) - => DisplayMessage($"{Emoji.Known.CheckMark} {message}", cancellationToken); + Task DisplaySuccess(string message, CancellationToken cancellationToken = default); Task DisplayStatusWithSuccess(string message, Func action, CancellationToken cancellationToken = default) { return DisplayStatus(message, async ct => @@ -20,8 +17,144 @@ Task DisplayStatusWithSuccess(string message, Func acti await DisplaySuccess(message, ct); }, cancellationToken); } - Task DisplayHeader(string header, CancellationToken cancellationToken = default); - Task DisplayNewLine(CancellationToken cancellationToken = default); +} + +public class LoggingDisplayProvider(ILogger logger) : IDisplayProvider +{ + public async Task DisplayStatus(string message, Func action, CancellationToken cancellationToken = default) + { + logger.Status(message); + await action(cancellationToken); + } + + public async Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default) + { + logger.Status(message); + return await action(cancellationToken); + } + + public async Task DisplaySuccess(string message, CancellationToken cancellationToken = default) + { + logger.Success(message); + await Task.CompletedTask; + } +} + +public static class KnownEvents +{ + public const int Status = 1; + public const int Success = 2; +} + +public static partial class LoggerExtensionMethods +{ + [LoggerMessage(KnownEvents.Status, LogLevel.Information, "{Message}")] + public static partial void Status(this ILogger logger, string message); + + [LoggerMessage(KnownEvents.Success, LogLevel.Information, "{Message}")] + public static partial void Success(this ILogger logger, string message); +} + +public interface IOutputProvider +{ + Task WriteLine(string output, CancellationToken cancellationToken); + + Task WriteMessage(string message, CancellationToken cancellationToken) + => WriteLine(RenderingHelpers.RenderMessage(message), cancellationToken); + + Task WriteNewLine(CancellationToken cancellationToken) + => WriteLine(string.Empty, cancellationToken); + + Task WriteHeader(string header, CancellationToken cancellationToken) + => WriteLine(RenderingHelpers.RenderHeader(header), cancellationToken); +} + +public class ConsoleOutputProvider : IOutputProvider +{ + public async Task WriteLine(string output, CancellationToken cancellationToken = default) + { + await Console.Out.WriteLineAsync(output.AsMemory(), cancellationToken); + } +} + +public static class RenderingHelpers +{ + public static string RenderTree(string header, IEnumerable> items, Func? itemFormatter = null) + where T : notnull + { + using var writer = new StringWriter(); + var console = AnsiConsole.Create(new AnsiConsoleSettings + { + Ansi = AnsiSupport.Detect, + ColorSystem = ColorSystemSupport.Detect, + Out = new AnsiConsoleOutput(writer) + }); + var tree = new Tree(header); + + foreach (var item in items) + { + var formattedItem = itemFormatter?.Invoke(item.Value) ?? item.Value.ToString(); + if (formattedItem is null) + { + continue; + } + var treeNode = tree.AddNode(formattedItem); + AddChildTreeNodes(treeNode, item, itemFormatter); + } + + console.Write(tree); + + return writer.ToString(); + } + + public static string RenderHeader(string header) + { + using var writer = new StringWriter(); + var console = AnsiConsole.Create(new AnsiConsoleSettings + { + Ansi = AnsiSupport.Detect, + ColorSystem = ColorSystemSupport.Detect, + Out = new AnsiConsoleOutput(writer) + }); + + var rule = new Rule(header); + rule.LeftJustified(); + rule.DoubleBorder(); + console.Write(rule); + + return writer.ToString(); + } + + public static string RenderMessage(string message) + { + using var writer = new StringWriter(); + var console = AnsiConsole.Create(new AnsiConsoleSettings + { + Ansi = AnsiSupport.Detect, + ColorSystem = ColorSystemSupport.Detect, + Out = new AnsiConsoleOutput(writer) + }); + + console.Markup(message); + return writer.ToString(); + } + + static void AddChildTreeNodes(TreeNode parent, TreeItem item, Func? itemFormatter = null) where T : notnull + { + foreach (var child in item.Children) + { + var formattedItem = itemFormatter?.Invoke(child.Value) ?? child.Value.ToString(); + if (formattedItem is null) + { + continue; + } + var node = parent.AddNode(formattedItem); + if (child.Children.Count > 0) + { + AddChildTreeNodes(node, child); + } + } + } } public class ConsoleDisplayProvider(IAnsiConsole console) : IDisplayProvider @@ -105,12 +238,6 @@ public async Task DisplayTree(string header, IEnumerable> items, console.Write(tree); } - public async Task DisplayMessage(string message, CancellationToken cancellationToken = default) - { - await Task.CompletedTask; - console.MarkupLine(message); - } - void AddChildTreeNodes(TreeNode parent, TreeItem item, Func? itemFormatter = null) where T : notnull { foreach (var child in item.Children) @@ -142,6 +269,12 @@ public async Task DisplayHeader(string header, CancellationToken cancellationTok rule.DoubleBorder(); console.Write(rule); } + + public async Task DisplaySuccess(string message, CancellationToken cancellationToken = default) + { + await Task.CompletedTask; + console.MarkupLine($"{Emoji.Known.CheckMark} {message}", cancellationToken); + } } public record TreeItem(T Value, List> Children); diff --git a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs index b531d25e..cd031add 100644 --- a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs +++ b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs @@ -9,6 +9,7 @@ using Stack.Infrastructure.Settings; using Stack.Commands; using Microsoft.Extensions.Logging; +using System.Security.Cryptography.X509Certificates; namespace Stack.Infrastructure; @@ -35,7 +36,15 @@ public static IHostApplicationBuilder ConfigureLogging(this IHostApplicationBuil builder.Logging.AddFilter("Microsoft", LogLevel.Warning); builder.Logging.ClearProviders(); - builder.Services.AddSingleton(); + + if (args.Contains(CommonOptions.Json.Name)) + { + builder.Logging.AddJsonConsole(); + } + else + { + builder.Services.AddSingleton(); + } return builder; } @@ -54,7 +63,15 @@ private static void ConfigureServices(this IServiceCollection services, string[] Out = new AnsiConsoleOutput(stream), }); }); - services.AddSingleton(); + services.AddSingleton(); + if (args.Contains(CommonOptions.Json.Name)) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); From 341c97571ec286ae95f226393ef9e05ef6e977e9 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 12 Sep 2025 18:25:39 +1000 Subject: [PATCH 06/11] Fix status display --- src/Stack/Commands/Remote/SyncStackCommand.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Stack/Commands/Remote/SyncStackCommand.cs b/src/Stack/Commands/Remote/SyncStackCommand.cs index ae26f7a9..4c9d50ce 100644 --- a/src/Stack/Commands/Remote/SyncStackCommand.cs +++ b/src/Stack/Commands/Remote/SyncStackCommand.cs @@ -104,9 +104,10 @@ await displayProvider.DisplayStatusWithSuccess("Fetching changes from remote rep if (!inputs.Confirm) { - await displayProvider.DisplayStatus("Checking stack status...", async (ct) => + var status = await displayProvider.DisplayStatus("Checking stack status...", async (ct) => { - var status = StackHelpers.GetStackStatus( + await Task.CompletedTask; + return StackHelpers.GetStackStatus( stack, currentBranch, logger, @@ -114,11 +115,9 @@ await displayProvider.DisplayStatus("Checking stack status...", async (ct) => gitClient, gitHubClient, true); - - await StackHelpers.OutputStackStatus(status, outputProvider, displayProvider, cancellationToken); }, cancellationToken); - await outputProvider.WriteNewLine(cancellationToken); + await StackHelpers.OutputStackStatus(status, outputProvider, displayProvider, cancellationToken); if (!await inputProvider.Confirm(Questions.ConfirmSyncStack, cancellationToken)) { From f7b806739bd12ac3ac15c8d6dfd86a5f0970e9ae Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 12 Sep 2025 19:18:13 +1000 Subject: [PATCH 07/11] Cleanup --- src/Stack/Commands/Branch/AddBranchCommand.cs | 10 +++++----- src/Stack/Commands/Branch/NewBranchCommand.cs | 10 +++++----- .../Commands/Branch/RemoveBranchCommand.cs | 10 +++++----- .../Commands/Config/OpenConfigCommand.cs | 10 +++++----- src/Stack/Commands/Helpers/StackActions.cs | 1 - src/Stack/Commands/Helpers/StackHelpers.cs | 11 +++------- .../PullRequests/CreatePullRequestsCommand.cs | 19 +++++++----------- .../PullRequests/OpenPullRequestsCommand.cs | 10 +++++----- src/Stack/Commands/Remote/PullStackCommand.cs | 10 +++++----- src/Stack/Commands/Remote/PushStackCommand.cs | 10 +++++----- src/Stack/Commands/Remote/SyncStackCommand.cs | 13 ++++++------ .../Commands/Stack/CleanupStackCommand.cs | 13 ++++++------ .../Commands/Stack/DeleteStackCommand.cs | 13 ++++++------ src/Stack/Commands/Stack/ListStacksCommand.cs | 15 ++++++-------- src/Stack/Commands/Stack/NewStackCommand.cs | 10 +++++----- .../Commands/Stack/RenameStackCommand.cs | 20 +++++++++---------- .../Commands/Stack/StackStatusCommand.cs | 20 ++++++++----------- .../Commands/Stack/StackSwitchCommand.cs | 10 +++++----- .../Commands/Stack/UpdateStackCommand.cs | 10 +++++----- src/Stack/Infrastructure/Commands/Command.cs | 12 +++++------ .../Commands/CommandWithOutput.cs | 14 ++++--------- 21 files changed, 112 insertions(+), 139 deletions(-) diff --git a/src/Stack/Commands/Branch/AddBranchCommand.cs b/src/Stack/Commands/Branch/AddBranchCommand.cs index f5df0d79..19047763 100644 --- a/src/Stack/Commands/Branch/AddBranchCommand.cs +++ b/src/Stack/Commands/Branch/AddBranchCommand.cs @@ -13,12 +13,12 @@ public class AddBranchCommand : Command private readonly AddBranchCommandHandler handler; public AddBranchCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + AddBranchCommandHandler handler, CliExecutionContext executionContext, - AddBranchCommandHandler handler) - : base("add", "Add an existing branch to a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("add", "Add an existing branch to a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); diff --git a/src/Stack/Commands/Branch/NewBranchCommand.cs b/src/Stack/Commands/Branch/NewBranchCommand.cs index b8af6098..5d820eeb 100644 --- a/src/Stack/Commands/Branch/NewBranchCommand.cs +++ b/src/Stack/Commands/Branch/NewBranchCommand.cs @@ -15,12 +15,12 @@ public class NewBranchCommand : Command private readonly NewBranchCommandHandler handler; public NewBranchCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + NewBranchCommandHandler handler, CliExecutionContext executionContext, - NewBranchCommandHandler handler) - : base("new", "Create a new branch in a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("new", "Create a new branch in a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); diff --git a/src/Stack/Commands/Branch/RemoveBranchCommand.cs b/src/Stack/Commands/Branch/RemoveBranchCommand.cs index 169e99ac..64112a74 100644 --- a/src/Stack/Commands/Branch/RemoveBranchCommand.cs +++ b/src/Stack/Commands/Branch/RemoveBranchCommand.cs @@ -25,12 +25,12 @@ public class RemoveBranchCommand : Command private readonly RemoveBranchCommandHandler handler; public RemoveBranchCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + RemoveBranchCommandHandler handler, CliExecutionContext executionContext, - RemoveBranchCommandHandler handler) - : base("remove", "Remove a branch from a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("remove", "Remove a branch from a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); diff --git a/src/Stack/Commands/Config/OpenConfigCommand.cs b/src/Stack/Commands/Config/OpenConfigCommand.cs index 6179a371..fe39fb9f 100644 --- a/src/Stack/Commands/Config/OpenConfigCommand.cs +++ b/src/Stack/Commands/Config/OpenConfigCommand.cs @@ -12,12 +12,12 @@ public class OpenConfigCommand : Command readonly IStackConfig stackConfig; public OpenConfigCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + IStackConfig stackConfig, CliExecutionContext executionContext, - IStackConfig stackConfig) - : base("open", "Open the configuration file in the default editor.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("open", "Open the configuration file in the default editor.", executionContext, inputProvider, outputProvider, logger) { this.stackConfig = stackConfig; } diff --git a/src/Stack/Commands/Helpers/StackActions.cs b/src/Stack/Commands/Helpers/StackActions.cs index 01d4fbe3..12feda13 100644 --- a/src/Stack/Commands/Helpers/StackActions.cs +++ b/src/Stack/Commands/Helpers/StackActions.cs @@ -116,7 +116,6 @@ public async Task UpdateStack(Config.Stack stack, UpdateStrategy strategy, Cance stack, currentBranch, logger, - displayProvider, gitClient, gitHubClient, true); diff --git a/src/Stack/Commands/Helpers/StackHelpers.cs b/src/Stack/Commands/Helpers/StackHelpers.cs index 56a3f85f..7c567cab 100644 --- a/src/Stack/Commands/Helpers/StackHelpers.cs +++ b/src/Stack/Commands/Helpers/StackHelpers.cs @@ -105,7 +105,6 @@ public static List GetStackStatus( List stacks, string currentBranch, ILogger logger, - IDisplayProvider displayProvider, IGitClient gitClient, IGitHubClient gitHubClient, bool includePullRequestStatus = true) @@ -218,7 +217,6 @@ public static StackStatus GetStackStatus( Config.Stack stack, string currentBranch, ILogger logger, - IDisplayProvider displayProvider, IGitClient gitClient, IGitHubClient gitHubClient, bool includePullRequestStatus = true) @@ -227,7 +225,6 @@ public static StackStatus GetStackStatus( [stack], currentBranch, logger, - displayProvider, gitClient, gitHubClient, includePullRequestStatus); @@ -238,19 +235,17 @@ public static StackStatus GetStackStatus( public static async Task OutputStackStatus( List statuses, IOutputProvider outputProvider, - IDisplayProvider displayProvider, CancellationToken cancellationToken) { foreach (var status in statuses) { - await OutputStackStatus(status, outputProvider, displayProvider, cancellationToken); + await OutputStackStatus(status, outputProvider, cancellationToken); } } public static async Task OutputStackStatus( StackStatus status, IOutputProvider outputProvider, - IDisplayProvider displayProvider, CancellationToken cancellationToken, Func? getBranchPullRequestDisplay = null) { @@ -523,10 +518,10 @@ public static async Task GetUpdateStrategy( return strategy; } - public static string[] GetBranchesNeedingCleanup(Config.Stack stack, ILogger logger, IDisplayProvider displayProvider, IGitClient gitClient, IGitHubClient gitHubClient) + public static string[] GetBranchesNeedingCleanup(Config.Stack stack, ILogger logger, IGitClient gitClient, IGitHubClient gitHubClient) { var currentBranch = gitClient.GetCurrentBranch(); - var stackStatus = GetStackStatus(stack, currentBranch, logger, displayProvider, gitClient, gitHubClient, true); + var stackStatus = GetStackStatus(stack, currentBranch, logger, gitClient, gitHubClient, true); return [.. stackStatus.GetAllBranches().Where(b => b.CouldBeCleanedUp).Select(b => b.Name)]; } diff --git a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs index 7791f3b9..1f67b6db 100644 --- a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs +++ b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs @@ -15,12 +15,12 @@ public class CreatePullRequestsCommand : Command private readonly CreatePullRequestsCommandHandler handler; public CreatePullRequestsCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + CreatePullRequestsCommandHandler handler, CliExecutionContext executionContext, - CreatePullRequestsCommandHandler handler) - : base("create", "Create pull requests for a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("create", "Create pull requests for a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); @@ -79,7 +79,6 @@ public override async Task Handle(CreatePullRequestsCommandInputs inputs, Cancel stack, currentBranch, logger, - displayProvider, gitClient, gitHubClient); @@ -105,7 +104,7 @@ public override async Task Handle(CreatePullRequestsCommandInputs inputs, Cancel } } - await StackHelpers.OutputStackStatus(status, outputProvider, displayProvider, cancellationToken); + await StackHelpers.OutputStackStatus(status, outputProvider, cancellationToken); await outputProvider.WriteNewLine(cancellationToken); @@ -139,7 +138,7 @@ public override async Task Handle(CreatePullRequestsCommandInputs inputs, Cancel await outputProvider.WriteNewLine(cancellationToken); - await OutputUpdatedStackStatus(logger, outputProvider, displayProvider, stack, status, pullRequestInformation, cancellationToken); + await OutputUpdatedStackStatus(outputProvider, status, pullRequestInformation, cancellationToken); await outputProvider.WriteNewLine(cancellationToken); @@ -201,10 +200,7 @@ private static List CreatePullRequests( } private static async Task OutputUpdatedStackStatus( - ILogger logger, IOutputProvider outputProvider, - IDisplayProvider displayProvider, - Config.Stack stack, StackStatus status, List pullRequestInformation, CancellationToken cancellationToken) @@ -212,7 +208,6 @@ private static async Task OutputUpdatedStackStatus( await StackHelpers.OutputStackStatus( status, outputProvider, - displayProvider, cancellationToken, (branch) => { diff --git a/src/Stack/Commands/PullRequests/OpenPullRequestsCommand.cs b/src/Stack/Commands/PullRequests/OpenPullRequestsCommand.cs index eb67fde9..b2d43166 100644 --- a/src/Stack/Commands/PullRequests/OpenPullRequestsCommand.cs +++ b/src/Stack/Commands/PullRequests/OpenPullRequestsCommand.cs @@ -13,12 +13,12 @@ public class OpenPullRequestsCommand : Command private readonly OpenPullRequestsCommandHandler handler; public OpenPullRequestsCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + OpenPullRequestsCommandHandler handler, CliExecutionContext executionContext, - OpenPullRequestsCommandHandler handler) - : base("open", "Open pull requests for a stack in the default browser.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("open", "Open pull requests for a stack in the default browser.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); diff --git a/src/Stack/Commands/Remote/PullStackCommand.cs b/src/Stack/Commands/Remote/PullStackCommand.cs index 73cb6efa..f17e9b12 100644 --- a/src/Stack/Commands/Remote/PullStackCommand.cs +++ b/src/Stack/Commands/Remote/PullStackCommand.cs @@ -13,12 +13,12 @@ public class PullStackCommand : Command private readonly PullStackCommandHandler handler; public PullStackCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + PullStackCommandHandler handler, CliExecutionContext executionContext, - PullStackCommandHandler handler) - : base("pull", "Pull changes from the remote repository for a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("pull", "Pull changes from the remote repository for a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); diff --git a/src/Stack/Commands/Remote/PushStackCommand.cs b/src/Stack/Commands/Remote/PushStackCommand.cs index 621f63dd..21f256eb 100644 --- a/src/Stack/Commands/Remote/PushStackCommand.cs +++ b/src/Stack/Commands/Remote/PushStackCommand.cs @@ -18,12 +18,12 @@ public class PushStackCommand : Command private readonly PushStackCommandHandler handler; public PushStackCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + PushStackCommandHandler handler, CliExecutionContext executionContext, - PushStackCommandHandler handler) - : base("push", "Push changes to the remote repository for a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("push", "Push changes to the remote repository for a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); diff --git a/src/Stack/Commands/Remote/SyncStackCommand.cs b/src/Stack/Commands/Remote/SyncStackCommand.cs index 4c9d50ce..d91df50f 100644 --- a/src/Stack/Commands/Remote/SyncStackCommand.cs +++ b/src/Stack/Commands/Remote/SyncStackCommand.cs @@ -19,12 +19,12 @@ public class SyncStackCommand : Command private readonly SyncStackCommandHandler handler; public SyncStackCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + SyncStackCommandHandler handler, CliExecutionContext executionContext, - SyncStackCommandHandler handler) - : base("sync", "Sync a stack with the remote repository. Shortcut for `git fetch --prune`, `stack pull`, `stack update` and `stack push`.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("sync", "Sync a stack with the remote repository. Shortcut for `git fetch --prune`, `stack pull`, `stack update` and `stack push`.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); @@ -111,13 +111,12 @@ await displayProvider.DisplayStatusWithSuccess("Fetching changes from remote rep stack, currentBranch, logger, - displayProvider, gitClient, gitHubClient, true); }, cancellationToken); - await StackHelpers.OutputStackStatus(status, outputProvider, displayProvider, cancellationToken); + await StackHelpers.OutputStackStatus(status, outputProvider, cancellationToken); if (!await inputProvider.Confirm(Questions.ConfirmSyncStack, cancellationToken)) { diff --git a/src/Stack/Commands/Stack/CleanupStackCommand.cs b/src/Stack/Commands/Stack/CleanupStackCommand.cs index 00a6b2c7..a4103734 100644 --- a/src/Stack/Commands/Stack/CleanupStackCommand.cs +++ b/src/Stack/Commands/Stack/CleanupStackCommand.cs @@ -13,12 +13,12 @@ public class CleanupStackCommand : Command private readonly CleanupStackCommandHandler handler; public CleanupStackCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + CleanupStackCommandHandler handler, CliExecutionContext executionContext, - CleanupStackCommandHandler handler) - : base("cleanup", "Clean up branches in a stack that are no longer needed.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("cleanup", "Clean up branches in a stack that are no longer needed.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); @@ -43,7 +43,6 @@ public record CleanupStackCommandInputs(string? Stack, bool Confirm) public class CleanupStackCommandHandler( IInputProvider inputProvider, ILogger logger, - IDisplayProvider displayProvider, IGitClient gitClient, IGitHubClient gitHubClient, IStackConfig stackConfig) @@ -66,7 +65,7 @@ public override async Task Handle(CleanupStackCommandInputs inputs, Cancellation throw new InvalidOperationException($"Stack '{inputs.Stack}' not found."); } - var branchesToCleanUp = StackHelpers.GetBranchesNeedingCleanup(stack, logger, displayProvider, gitClient, gitHubClient); + var branchesToCleanUp = StackHelpers.GetBranchesNeedingCleanup(stack, logger, gitClient, gitHubClient); if (branchesToCleanUp.Length == 0) { diff --git a/src/Stack/Commands/Stack/DeleteStackCommand.cs b/src/Stack/Commands/Stack/DeleteStackCommand.cs index 6db441b2..6d5bdb21 100644 --- a/src/Stack/Commands/Stack/DeleteStackCommand.cs +++ b/src/Stack/Commands/Stack/DeleteStackCommand.cs @@ -14,12 +14,12 @@ public class DeleteStackCommand : Command private readonly DeleteStackCommandHandler handler; public DeleteStackCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + DeleteStackCommandHandler handler, CliExecutionContext executionContext, - DeleteStackCommandHandler handler) - : base("delete", "Delete a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("delete", "Delete a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); @@ -46,7 +46,6 @@ public record DeleteStackCommandResponse(string? DeletedStackName); public class DeleteStackCommandHandler( IInputProvider inputProvider, ILogger logger, - IDisplayProvider displayProvider, IGitClient gitClient, IGitHubClient gitHubClient, IStackConfig stackConfig) @@ -71,7 +70,7 @@ public override async Task Handle(DeleteStackCommandInputs inputs, CancellationT if (inputs.Confirm || await inputProvider.Confirm(Questions.ConfirmDeleteStack, cancellationToken)) { - var branchesNeedingCleanup = StackHelpers.GetBranchesNeedingCleanup(stack, logger, displayProvider, gitClient, gitHubClient); + var branchesNeedingCleanup = StackHelpers.GetBranchesNeedingCleanup(stack, logger, gitClient, gitHubClient); if (branchesNeedingCleanup.Length > 0) { diff --git a/src/Stack/Commands/Stack/ListStacksCommand.cs b/src/Stack/Commands/Stack/ListStacksCommand.cs index c5e4a687..76a2bffb 100644 --- a/src/Stack/Commands/Stack/ListStacksCommand.cs +++ b/src/Stack/Commands/Stack/ListStacksCommand.cs @@ -20,19 +20,16 @@ internal partial class ListStacksCommandJsonSerializerContext : JsonSerializerCo public class ListStacksCommand : CommandWithOutput { private readonly ListStacksCommandHandler handler; - private readonly IOutputProvider outputProvider; public ListStacksCommand( - ILogger logger, - IOutputProvider outputProvider, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + ListStacksCommandHandler handler, CliExecutionContext executionContext, - ListStacksCommandHandler handler) - : base("list", "List stacks.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("list", "List stacks.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; - this.outputProvider = outputProvider; } protected override async Task ExecuteAndReturnResponse(ParseResult parseResult, CancellationToken cancellationToken) @@ -50,7 +47,7 @@ protected override async Task WriteDefaultOutput(ListStacksCommandResponse respo foreach (var stack in response.Stacks) { - await outputProvider.WriteMessage($"{stack.Name.Stack()} {$"({stack.SourceBranch})".Muted()} {stack.BranchCount} {(stack.BranchCount == 1 ? "branch" : "branches")}", cancellationToken); + await OutputProvider.WriteMessage($"{stack.Name.Stack()} {$"({stack.SourceBranch})".Muted()} {stack.BranchCount} {(stack.BranchCount == 1 ? "branch" : "branches")}", cancellationToken); } } diff --git a/src/Stack/Commands/Stack/NewStackCommand.cs b/src/Stack/Commands/Stack/NewStackCommand.cs index 31a525db..db92cee2 100644 --- a/src/Stack/Commands/Stack/NewStackCommand.cs +++ b/src/Stack/Commands/Stack/NewStackCommand.cs @@ -43,12 +43,12 @@ public class NewStackCommand : Command private readonly NewStackCommandHandler handler; public NewStackCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + NewStackCommandHandler handler, CliExecutionContext executionContext, - NewStackCommandHandler handler) - : base("new", "Create a new stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("new", "Create a new stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(StackName); diff --git a/src/Stack/Commands/Stack/RenameStackCommand.cs b/src/Stack/Commands/Stack/RenameStackCommand.cs index d9abf7d9..d54e2f2f 100644 --- a/src/Stack/Commands/Stack/RenameStackCommand.cs +++ b/src/Stack/Commands/Stack/RenameStackCommand.cs @@ -15,16 +15,16 @@ public class RenameStackCommand : Command Description = "The new name for the stack.", Required = false }; - + private readonly RenameStackCommandHandler handler; public RenameStackCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + RenameStackCommandHandler handler, CliExecutionContext executionContext, - RenameStackCommandHandler handler) - : base("rename", "Rename a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("rename", "Rename a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); @@ -80,8 +80,8 @@ public override async Task Handle(RenameStackCommandInputs inputs, CancellationT var newName = await inputProvider.Text(logger, Questions.StackName, inputs.Name, cancellationToken); // Validate that there's not another stack with the same name for the same remote - var existingStackWithSameName = stacksForRemote.FirstOrDefault(s => - s.Name.Equals(newName, StringComparison.OrdinalIgnoreCase) && + var existingStackWithSameName = stacksForRemote.FirstOrDefault(s => + s.Name.Equals(newName, StringComparison.OrdinalIgnoreCase) && !s.Name.Equals(stack.Name, StringComparison.OrdinalIgnoreCase)); if (existingStackWithSameName is not null) @@ -90,11 +90,11 @@ public override async Task Handle(RenameStackCommandInputs inputs, CancellationT } var renamedStack = stack.ChangeName(newName); - + // Update the stack in the collection var stackIndex = stackData.Stacks.IndexOf(stack); stackData.Stacks[stackIndex] = renamedStack; - + stackConfig.Save(stackData); logger.StackRenamed(stack.Name, newName); diff --git a/src/Stack/Commands/Stack/StackStatusCommand.cs b/src/Stack/Commands/Stack/StackStatusCommand.cs index 1909b333..f7c1b1be 100644 --- a/src/Stack/Commands/Stack/StackStatusCommand.cs +++ b/src/Stack/Commands/Stack/StackStatusCommand.cs @@ -94,19 +94,16 @@ public class StackStatusCommand : CommandWithOutput }; private readonly StackStatusCommandHandler handler; - private readonly IOutputProvider outputProvider; public StackStatusCommand( - ILogger logger, - IOutputProvider outputProvider, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + StackStatusCommandHandler handler, CliExecutionContext executionContext, - StackStatusCommandHandler handler) - : base("status", "Show the status of the current stack or all stacks in the repository.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("status", "Show the status of the current stack or all stacks in the repository.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; - this.outputProvider = outputProvider; Add(CommonOptions.Stack); Add(All); Add(Full); @@ -124,12 +121,12 @@ protected override async Task ExecuteAndReturnRespon protected override async Task WriteDefaultOutput(StackStatusCommandResponse response, CancellationToken cancellationToken) { - await StackHelpers.OutputStackStatus(response.Stacks, outputProvider, DisplayProvider, cancellationToken); + await StackHelpers.OutputStackStatus(response.Stacks, OutputProvider, cancellationToken); if (response.Stacks.Count == 1) { var stack = response.Stacks.First(); - await StackHelpers.OutputBranchAndStackActions(stack, outputProvider, cancellationToken); + await StackHelpers.OutputBranchAndStackActions(stack, OutputProvider, cancellationToken); } } @@ -137,7 +134,7 @@ protected override async Task WriteJsonOutput(StackStatusCommandResponse respons { var output = response.Stacks.Select(MapToJsonOutput).ToList(); var json = JsonSerializer.Serialize(output, typeof(List), StackStatusCommandJsonSerializerContext.Default); - await outputProvider.WriteLine(json, cancellationToken); + await OutputProvider.WriteLine(json, cancellationToken); } private static StackStatusCommandJsonOutput MapToJsonOutput(StackStatus stack) @@ -254,7 +251,6 @@ public override async Task Handle(StackStatusCommand stacksToCheckStatusFor, currentBranch, logger, - displayProvider, gitClient, gitHubClient, inputs.Full); diff --git a/src/Stack/Commands/Stack/StackSwitchCommand.cs b/src/Stack/Commands/Stack/StackSwitchCommand.cs index 161b455b..d8972d06 100644 --- a/src/Stack/Commands/Stack/StackSwitchCommand.cs +++ b/src/Stack/Commands/Stack/StackSwitchCommand.cs @@ -14,12 +14,12 @@ public class StackSwitchCommand : Command private readonly StackSwitchCommandHandler handler; public StackSwitchCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + StackSwitchCommandHandler handler, CliExecutionContext executionContext, - StackSwitchCommandHandler handler) - : base("switch", "Switch to a branch in a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("switch", "Switch to a branch in a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Branch); diff --git a/src/Stack/Commands/Stack/UpdateStackCommand.cs b/src/Stack/Commands/Stack/UpdateStackCommand.cs index 3a35c2a5..fe249a06 100644 --- a/src/Stack/Commands/Stack/UpdateStackCommand.cs +++ b/src/Stack/Commands/Stack/UpdateStackCommand.cs @@ -13,12 +13,12 @@ public class UpdateStackCommand : Command private readonly UpdateStackCommandHandler handler; public UpdateStackCommand( - ILogger logger, - IDisplayProvider displayProvider, - IInputProvider inputProvider, + UpdateStackCommandHandler handler, CliExecutionContext executionContext, - UpdateStackCommandHandler handler) - : base("update", "Update the branches in a stack.", logger, displayProvider, inputProvider, executionContext) + IInputProvider inputProvider, + IOutputProvider outputProvider, + ILogger logger) + : base("update", "Update the branches in a stack.", executionContext, inputProvider, outputProvider, logger) { this.handler = handler; Add(CommonOptions.Stack); diff --git a/src/Stack/Infrastructure/Commands/Command.cs b/src/Stack/Infrastructure/Commands/Command.cs index d2d0f0c7..04d3092c 100644 --- a/src/Stack/Infrastructure/Commands/Command.cs +++ b/src/Stack/Infrastructure/Commands/Command.cs @@ -11,21 +11,21 @@ public abstract class Command : System.CommandLine.Command { protected ILogger Logger; protected IInputProvider InputProvider; - protected IDisplayProvider DisplayProvider; + protected IOutputProvider OutputProvider; protected string? WorkingDirectory; protected bool Verbose; public Command( string name, string? description, - ILogger logger, - IDisplayProvider displayProvider, + CliExecutionContext executionContext, IInputProvider inputProvider, - CliExecutionContext executionContext) : base(name, description) + IOutputProvider outputProvider, + ILogger logger) : base(name, description) { - Logger = logger; - DisplayProvider = displayProvider; InputProvider = inputProvider; + OutputProvider = outputProvider; + Logger = logger; Add(CommonOptions.WorkingDirectory); Add(CommonOptions.Debug); diff --git a/src/Stack/Infrastructure/Commands/CommandWithOutput.cs b/src/Stack/Infrastructure/Commands/CommandWithOutput.cs index 6fa27ed1..0bd80c0a 100644 --- a/src/Stack/Infrastructure/Commands/CommandWithOutput.cs +++ b/src/Stack/Infrastructure/Commands/CommandWithOutput.cs @@ -11,20 +11,14 @@ public abstract class CommandWithOutput : Command where TResponse : n protected CommandWithOutput( string name, string? description, - ILogger logger, - IDisplayProvider displayProvider, + CliExecutionContext executionContext, IInputProvider inputProvider, - CliExecutionContext executionContext) - : base(name, description, logger, displayProvider, inputProvider, executionContext) + IOutputProvider outputProvider, + ILogger logger) + : base(name, description, executionContext, inputProvider, outputProvider, logger) { - Add(CommonOptions.Json); } - readonly JsonSerializerOptions jsonSerializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - protected override async Task Execute(ParseResult parseResult, CancellationToken cancellationToken) { var json = parseResult.GetValue(CommonOptions.Json); From a830cc3ac415b1a6b8b684ec3d6ed7ac12c88f07 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 12 Sep 2025 20:50:51 +1000 Subject: [PATCH 08/11] More fixes --- .../CreatePullRequestsCommandHandlerTests.cs | 45 ++++++++++--------- .../Helpers/TestDisplayProvider.cs | 6 +++ .../HostApplicationBuilderExtensions.cs | 5 +++ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/Stack.Tests/Commands/PullRequests/CreatePullRequestsCommandHandlerTests.cs b/src/Stack.Tests/Commands/PullRequests/CreatePullRequestsCommandHandlerTests.cs index d324239d..ab95aaf8 100644 --- a/src/Stack.Tests/Commands/PullRequests/CreatePullRequestsCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/PullRequests/CreatePullRequestsCommandHandlerTests.cs @@ -39,7 +39,8 @@ public async Task WhenNoPullRequestsExistForAStackWithMultipleBranches_CreatesPu .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -52,7 +53,7 @@ public async Task WhenNoPullRequestsExistForAStackWithMultipleBranches_CreatesPu [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider @@ -109,7 +110,7 @@ public async Task WhenCreatingPullRequestsForAStackWithMultipleBranches_EachPull .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -122,7 +123,7 @@ public async Task WhenCreatingPullRequestsForAStackWithMultipleBranches_EachPull [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -170,7 +171,7 @@ public async Task WhenAPullRequestExistForABranch_AndNoneForAnotherBranch_Create .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -183,7 +184,7 @@ public async Task WhenAPullRequestExistForABranch_AndNoneForAnotherBranch_Create [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider @@ -228,7 +229,7 @@ public async Task WhenStackNameIsProvided_PullRequestsAreCreatedForThatStack() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -241,7 +242,7 @@ public async Task WhenStackNameIsProvided_PullRequestsAreCreatedForThatStack() [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -288,7 +289,7 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName_PullRequestsAreC .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -301,7 +302,7 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName_PullRequestsAreC [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider @@ -352,7 +353,7 @@ public async Task WhenStackNameIsProvided_ButTheStackDoesNotExist_Throws() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -365,7 +366,7 @@ public async Task WhenStackNameIsProvided_ButTheStackDoesNotExist_Throws() [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); // Act and assert @@ -405,7 +406,7 @@ public async Task WhenAPullRequestExistForABranch_AndHasBeenMerged_AndNoneForAno .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -418,7 +419,7 @@ public async Task WhenAPullRequestExistForABranch_AndHasBeenMerged_AndNoneForAno [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -469,7 +470,7 @@ public async Task WhenAPullRequestTemplateExistsInTheRepo_ItIsUsedAsTheBodyOfANe .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -483,7 +484,7 @@ public async Task WhenAPullRequestTemplateExistsInTheRepo_ItIsUsedAsTheBodyOfANe [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); Directory.CreateDirectory(Path.Join(tempRepo.DirectoryPath, ".github")); File.WriteAllText(Path.Join(tempRepo.DirectoryPath, ".github", "PULL_REQUEST_TEMPLATE.md"), "This is the PR template"); @@ -535,7 +536,7 @@ public async Task WhenAPullRequestTemplateDoesNotExistInTheRepo_TheStackPrListMa .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -545,7 +546,7 @@ public async Task WhenAPullRequestTemplateDoesNotExistInTheRepo_TheStackPrListMa [sourceBranch] = new GitBranchStatus(sourceBranch, $"origin/{sourceBranch}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -597,7 +598,7 @@ public async Task WhenAskedWhetherToCreateAPullRequestAsADraft_AndTheAnswerIsYes .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -610,7 +611,7 @@ public async Task WhenAskedWhetherToCreateAPullRequestAsADraft_AndTheAnswerIsYes [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -652,7 +653,7 @@ public async Task WhenOnlySelectingSomeBranchesToCreatePullRequestsFor_OnlyThose .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -665,7 +666,7 @@ public async Task WhenOnlySelectingSomeBranchesToCreatePullRequestsFor_OnlyThose [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); diff --git a/src/Stack.Tests/Helpers/TestDisplayProvider.cs b/src/Stack.Tests/Helpers/TestDisplayProvider.cs index 3109d113..661a0f22 100644 --- a/src/Stack.Tests/Helpers/TestDisplayProvider.cs +++ b/src/Stack.Tests/Helpers/TestDisplayProvider.cs @@ -46,4 +46,10 @@ public async Task DisplayHeader(string header, CancellationToken cancellationTok await Task.CompletedTask; testOutputHelper.WriteLine($"HEADER: {header}"); } + + public async Task DisplaySuccess(string message, CancellationToken cancellationToken = default) + { + await Task.CompletedTask; + testOutputHelper.WriteLine($"SUCCESS: {message}"); + } } diff --git a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs index cd031add..4772a40f 100644 --- a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs +++ b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs @@ -10,6 +10,7 @@ using Stack.Commands; using Microsoft.Extensions.Logging; using System.Security.Cryptography.X509Certificates; +using Microsoft.Extensions.Logging.Console; namespace Stack.Infrastructure; @@ -40,6 +41,10 @@ public static IHostApplicationBuilder ConfigureLogging(this IHostApplicationBuil if (args.Contains(CommonOptions.Json.Name)) { builder.Logging.AddJsonConsole(); + builder.Services.Configure(options => + { + options.LogToStandardErrorThreshold = LogLevel.Trace; + }); } else { From 92ec305f77d5cdf77ed1b47f5fe605b0dd2cd6be Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Sat, 13 Sep 2025 12:37:18 +1000 Subject: [PATCH 09/11] Fix tests --- .../CreatePullRequestsCommandHandlerTests.cs | 30 +++++--- .../Remote/SyncStackCommandHandlerTests.cs | 75 +++++++++++-------- .../Stack/CleanupStackCommandHandlerTests.cs | 27 +++---- .../Stack/DeleteStackCommandHandlerTests.cs | 21 ++---- 4 files changed, 81 insertions(+), 72 deletions(-) diff --git a/src/Stack.Tests/Commands/PullRequests/CreatePullRequestsCommandHandlerTests.cs b/src/Stack.Tests/Commands/PullRequests/CreatePullRequestsCommandHandlerTests.cs index ab95aaf8..049d73bd 100644 --- a/src/Stack.Tests/Commands/PullRequests/CreatePullRequestsCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/PullRequests/CreatePullRequestsCommandHandlerTests.cs @@ -111,6 +111,7 @@ public async Task WhenCreatingPullRequestsForAStackWithMultipleBranches_EachPull var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -123,7 +124,7 @@ public async Task WhenCreatingPullRequestsForAStackWithMultipleBranches_EachPull [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -172,6 +173,7 @@ public async Task WhenAPullRequestExistForABranch_AndNoneForAnotherBranch_Create var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -184,7 +186,7 @@ public async Task WhenAPullRequestExistForABranch_AndNoneForAnotherBranch_Create [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider @@ -230,6 +232,7 @@ public async Task WhenStackNameIsProvided_PullRequestsAreCreatedForThatStack() var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -242,7 +245,7 @@ public async Task WhenStackNameIsProvided_PullRequestsAreCreatedForThatStack() [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -290,6 +293,7 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName_PullRequestsAreC var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -302,7 +306,7 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName_PullRequestsAreC [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider @@ -354,6 +358,7 @@ public async Task WhenStackNameIsProvided_ButTheStackDoesNotExist_Throws() var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -366,7 +371,7 @@ public async Task WhenStackNameIsProvided_ButTheStackDoesNotExist_Throws() [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); // Act and assert @@ -407,6 +412,7 @@ public async Task WhenAPullRequestExistForABranch_AndHasBeenMerged_AndNoneForAno var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -419,7 +425,7 @@ public async Task WhenAPullRequestExistForABranch_AndHasBeenMerged_AndNoneForAno [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -471,6 +477,7 @@ public async Task WhenAPullRequestTemplateExistsInTheRepo_ItIsUsedAsTheBodyOfANe var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -484,7 +491,7 @@ public async Task WhenAPullRequestTemplateExistsInTheRepo_ItIsUsedAsTheBodyOfANe [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); Directory.CreateDirectory(Path.Join(tempRepo.DirectoryPath, ".github")); File.WriteAllText(Path.Join(tempRepo.DirectoryPath, ".github", "PULL_REQUEST_TEMPLATE.md"), "This is the PR template"); @@ -537,6 +544,7 @@ public async Task WhenAPullRequestTemplateDoesNotExistInTheRepo_TheStackPrListMa var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -546,7 +554,7 @@ public async Task WhenAPullRequestTemplateDoesNotExistInTheRepo_TheStackPrListMa [sourceBranch] = new GitBranchStatus(sourceBranch, $"origin/{sourceBranch}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -599,6 +607,7 @@ public async Task WhenAskedWhetherToCreateAPullRequestAsADraft_AndTheAnswerIsYes var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -611,7 +620,7 @@ public async Task WhenAskedWhetherToCreateAPullRequestAsADraft_AndTheAnswerIsYes [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); @@ -654,6 +663,7 @@ public async Task WhenOnlySelectingSomeBranchesToCreatePullRequestsFor_OnlyThose var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); var fileOperations = new FileOperations(); var gitClient = Substitute.For(); gitClient.GetRemoteUri().Returns(remoteUri); @@ -666,7 +676,7 @@ public async Task WhenOnlySelectingSomeBranchesToCreatePullRequestsFor_OnlyThose [branch1] = new GitBranchStatus(branch1, $"origin/{branch1}", true, false, 0, 0, new Commit(Some.Sha(), "msg")), [branch2] = new GitBranchStatus(branch2, $"origin/{branch2}", true, false, 0, 0, new Commit(Some.Sha(), "msg")) }); - var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); + var handler = new CreatePullRequestsCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, fileOperations, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); diff --git a/src/Stack.Tests/Commands/Remote/SyncStackCommandHandlerTests.cs b/src/Stack.Tests/Commands/Remote/SyncStackCommandHandlerTests.cs index 82ed1106..a0ad22ca 100644 --- a/src/Stack.Tests/Commands/Remote/SyncStackCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/Remote/SyncStackCommandHandlerTests.cs @@ -39,8 +39,9 @@ public async Task WhenNameIsProvided_DoesNotAskForName_SyncsCorrectStack() var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -92,8 +93,9 @@ public async Task WhenNameIsProvided_ButStackDoesNotExist_Throws() var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -131,8 +133,9 @@ public async Task WhenOnASpecificBranchInTheStack_TheSameBranchIsSetAsCurrentAft var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); // We are on a specific branch in the stack gitClient.GetRemoteUri().Returns(remoteUri); @@ -181,8 +184,9 @@ public async Task WhenOnlyASingleStackExists_DoesNotAskForStackName_SyncsStack() var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -234,8 +238,9 @@ public async Task WhenRebaseIsProvided_SyncsStackUsingRebase() var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -285,8 +290,9 @@ public async Task WhenMergeIsProvided_SyncsStackUsingMerge() var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -335,8 +341,9 @@ public async Task WhenNotSpecifyingRebaseOrMerge_AndUpdateSettingIsRebase_SyncsS var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -386,8 +393,9 @@ public async Task WhenNotSpecifyingRebaseOrMerge_AndUpdateSettingIsMerge_SyncsSt var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -437,8 +445,9 @@ public async Task WhenGitConfigValueIsSetToMerge_ButRebaseIsSpecified_SyncsStack var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -488,8 +497,9 @@ public async Task WhenGitConfigValueIsSetToRebase_ButMergeIsSpecified_SyncsStack var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -539,8 +549,9 @@ public async Task WhenNotSpecifyingRebaseOrMerge_AndNoUpdateSettingExists_AndMer var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -591,8 +602,9 @@ public async Task WhenNotSpecifyingRebaseOrMerge_AndNoUpdateSettingsExists_AndRe var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -628,8 +640,9 @@ public async Task WhenBothRebaseAndMergeAreSpecified_AnErrorIsThrown() var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); // Act and assert await handler @@ -664,8 +677,9 @@ public async Task WhenConfirmOptionIsProvided_DoesNotAskForConfirmation() var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -716,8 +730,9 @@ public async Task WhenNoPushOptionIsProvided_DoesNotPushChangesToRemote() var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new SyncStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig, stackActions); + var displayProvider = new TestDisplayProvider(testOutputHelper); + var outputProvider = Substitute.For(); + var handler = new SyncStackCommandHandler(inputProvider, logger, outputProvider, displayProvider, gitClient, gitHubClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); diff --git a/src/Stack.Tests/Commands/Stack/CleanupStackCommandHandlerTests.cs b/src/Stack.Tests/Commands/Stack/CleanupStackCommandHandlerTests.cs index 9cd86e9a..a1057c57 100644 --- a/src/Stack.Tests/Commands/Stack/CleanupStackCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/Stack/CleanupStackCommandHandlerTests.cs @@ -36,10 +36,9 @@ public async Task WhenBranchExistsLocally_ButHasNotBeenPushedToTheRemote_BranchI .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); - var handler = new CleanupStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new CleanupStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); @@ -86,10 +85,9 @@ public async Task WhenBranchExistsLocally_AndHasBeenDeletedFromTheRemote_BranchI .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); - var handler = new CleanupStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new CleanupStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); @@ -136,10 +134,9 @@ public async Task WhenBranchExistsLocally_AndInRemote_BranchIsNotDeletedLocally( .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); - var handler = new CleanupStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new CleanupStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); @@ -189,8 +186,7 @@ public async Task WhenConfirmationIsFalse_DoesNotDeleteAnyBranches() var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new CleanupStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new CleanupStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); @@ -239,8 +235,7 @@ public async Task WhenStackNameIsProvided_ItIsNotAskedFor() var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new CleanupStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new CleanupStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); @@ -288,8 +283,7 @@ public async Task WhenStackNameIsProvided_ButStackDoesNotExist_Throws() var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new CleanupStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new CleanupStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); @@ -325,8 +319,7 @@ public async Task WhenOnlyASingleStackExists_StackIsSelectedAutomatically() var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new CleanupStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new CleanupStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); @@ -374,8 +367,7 @@ public async Task WhenConfirmIsProvided_DoesNotAskForConfirmation_DeletesBranche var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new CleanupStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new CleanupStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); @@ -425,8 +417,7 @@ public async Task WhenChildBranchExistsLocally_AndHasBeenDeletedFromTheRemote_Br var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); - var console = new TestDisplayProvider(testOutputHelper); - var handler = new CleanupStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new CleanupStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); diff --git a/src/Stack.Tests/Commands/Stack/DeleteStackCommandHandlerTests.cs b/src/Stack.Tests/Commands/Stack/DeleteStackCommandHandlerTests.cs index 8750a3b4..28e5da56 100644 --- a/src/Stack.Tests/Commands/Stack/DeleteStackCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/Stack/DeleteStackCommandHandlerTests.cs @@ -33,7 +33,6 @@ public async Task WhenNoInputsAreProvided_AsksForName_AndConfirmation_AndDeletes var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); @@ -41,7 +40,7 @@ public async Task WhenNoInputsAreProvided_AsksForName_AndConfirmation_AndDeletes gitClient.GetCurrentBranch().Returns(sourceBranch); gitClient.GetBranchStatuses(Arg.Any()).Returns(ci => CreateBranchStatuses(ci.Arg(), sourceBranch)); - var handler = new DeleteStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new DeleteStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider.Confirm(Questions.ConfirmDeleteStack, Arg.Any()).Returns(true); @@ -75,7 +74,6 @@ public async Task WhenConfirmationIsFalse_DoesNotDeleteStack() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); @@ -83,7 +81,7 @@ public async Task WhenConfirmationIsFalse_DoesNotDeleteStack() gitClient.GetCurrentBranch().Returns(sourceBranch); gitClient.GetBranchStatuses(Arg.Any()).Returns(ci => CreateBranchStatuses(ci.Arg(), sourceBranch)); - var handler = new DeleteStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new DeleteStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider.Confirm(Questions.ConfirmDeleteStack, Arg.Any()).Returns(false); @@ -118,7 +116,6 @@ public async Task WhenNameIsProvided_AsksForConfirmation_AndDeletesStack() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); @@ -126,7 +123,7 @@ public async Task WhenNameIsProvided_AsksForConfirmation_AndDeletesStack() gitClient.GetCurrentBranch().Returns(sourceBranch); gitClient.GetBranchStatuses(Arg.Any()).Returns(ci => CreateBranchStatuses(ci.Arg(), sourceBranch)); - var handler = new DeleteStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new DeleteStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); inputProvider.Confirm(Questions.ConfirmDeleteStack, Arg.Any()).Returns(true); @@ -161,7 +158,6 @@ public async Task WhenStackDoesNotExist_Throws() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); @@ -169,7 +165,7 @@ public async Task WhenStackDoesNotExist_Throws() gitClient.GetCurrentBranch().Returns(sourceBranch); gitClient.GetBranchStatuses(Arg.Any()).Returns(ci => CreateBranchStatuses(ci.Arg(), sourceBranch)); - var handler = new DeleteStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new DeleteStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); inputProvider.Confirm(Questions.ConfirmDeleteStack, Arg.Any()).Returns(true); @@ -203,7 +199,6 @@ public async Task WhenThereAreLocalBranchesThatAreDeletedInTheRemote_AsksToClean .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); @@ -228,7 +223,7 @@ public async Task WhenThereAreLocalBranchesThatAreDeletedInTheRemote_AsksToClean return dict; }); - var handler = new DeleteStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new DeleteStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider.Confirm(Questions.ConfirmDeleteStack, Arg.Any()).Returns(true); @@ -261,7 +256,6 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); @@ -269,7 +263,7 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName() gitClient.GetCurrentBranch().Returns(sourceBranch); gitClient.GetBranchStatuses(Arg.Any()).Returns(ci => CreateBranchStatuses(ci.Arg(), sourceBranch)); - var handler = new DeleteStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new DeleteStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); inputProvider.Confirm(Questions.ConfirmDeleteStack, Arg.Any()).Returns(true); @@ -301,7 +295,6 @@ public async Task WhenConfirmIsProvided_DoesNotAskForConfirmation_DeletesStack() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); - var console = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var gitHubClient = Substitute.For(); @@ -309,7 +302,7 @@ public async Task WhenConfirmIsProvided_DoesNotAskForConfirmation_DeletesStack() gitClient.GetCurrentBranch().Returns(sourceBranch); gitClient.GetBranchStatuses(Arg.Any()).Returns(ci => CreateBranchStatuses(ci.Arg(), sourceBranch)); - var handler = new DeleteStackCommandHandler(inputProvider, logger, console, gitClient, gitHubClient, stackConfig); + var handler = new DeleteStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); From 97c4cf22029521e946a75be7f25dc8cff886b518 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Wed, 17 Sep 2025 08:48:10 +1000 Subject: [PATCH 10/11] Cleanup --- global.json | 6 - .../Infrastructure/ConsoleDisplayProvider.cs | 155 ------------------ .../Infrastructure/ConsoleOutputProvider.cs | 9 + src/Stack/Infrastructure/IDisplayProvider.cs | 16 ++ src/Stack/Infrastructure/IOutputProvider.cs | 15 ++ .../Infrastructure/LoggingDisplayProvider.cs | 39 +++++ src/Stack/Infrastructure/RenderingHelpers.cs | 83 ++++++++++ 7 files changed, 162 insertions(+), 161 deletions(-) delete mode 100644 global.json create mode 100644 src/Stack/Infrastructure/ConsoleOutputProvider.cs create mode 100644 src/Stack/Infrastructure/IDisplayProvider.cs create mode 100644 src/Stack/Infrastructure/IOutputProvider.cs create mode 100644 src/Stack/Infrastructure/LoggingDisplayProvider.cs create mode 100644 src/Stack/Infrastructure/RenderingHelpers.cs diff --git a/global.json b/global.json deleted file mode 100644 index ff07225f..00000000 --- a/global.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "sdk": { - "version": "9.0.203", - "rollForward": "latestFeature" - } -} diff --git a/src/Stack/Infrastructure/ConsoleDisplayProvider.cs b/src/Stack/Infrastructure/ConsoleDisplayProvider.cs index c7eeb4e8..f08c514d 100644 --- a/src/Stack/Infrastructure/ConsoleDisplayProvider.cs +++ b/src/Stack/Infrastructure/ConsoleDisplayProvider.cs @@ -1,162 +1,7 @@ -using Microsoft.Extensions.Logging; using Spectre.Console; -using Spectre.Console.Rendering; namespace Stack.Infrastructure; -public interface IDisplayProvider -{ - Task DisplayStatus(string message, Func action, CancellationToken cancellationToken = default); - Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default); - Task DisplaySuccess(string message, CancellationToken cancellationToken = default); - Task DisplayStatusWithSuccess(string message, Func action, CancellationToken cancellationToken = default) - { - return DisplayStatus(message, async ct => - { - await action(ct); - await DisplaySuccess(message, ct); - }, cancellationToken); - } -} - -public class LoggingDisplayProvider(ILogger logger) : IDisplayProvider -{ - public async Task DisplayStatus(string message, Func action, CancellationToken cancellationToken = default) - { - logger.Status(message); - await action(cancellationToken); - } - - public async Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default) - { - logger.Status(message); - return await action(cancellationToken); - } - - public async Task DisplaySuccess(string message, CancellationToken cancellationToken = default) - { - logger.Success(message); - await Task.CompletedTask; - } -} - -public static class KnownEvents -{ - public const int Status = 1; - public const int Success = 2; -} - -public static partial class LoggerExtensionMethods -{ - [LoggerMessage(KnownEvents.Status, LogLevel.Information, "{Message}")] - public static partial void Status(this ILogger logger, string message); - - [LoggerMessage(KnownEvents.Success, LogLevel.Information, "{Message}")] - public static partial void Success(this ILogger logger, string message); -} - -public interface IOutputProvider -{ - Task WriteLine(string output, CancellationToken cancellationToken); - - Task WriteMessage(string message, CancellationToken cancellationToken) - => WriteLine(RenderingHelpers.RenderMessage(message), cancellationToken); - - Task WriteNewLine(CancellationToken cancellationToken) - => WriteLine(string.Empty, cancellationToken); - - Task WriteHeader(string header, CancellationToken cancellationToken) - => WriteLine(RenderingHelpers.RenderHeader(header), cancellationToken); -} - -public class ConsoleOutputProvider : IOutputProvider -{ - public async Task WriteLine(string output, CancellationToken cancellationToken = default) - { - await Console.Out.WriteLineAsync(output.AsMemory(), cancellationToken); - } -} - -public static class RenderingHelpers -{ - public static string RenderTree(string header, IEnumerable> items, Func? itemFormatter = null) - where T : notnull - { - using var writer = new StringWriter(); - var console = AnsiConsole.Create(new AnsiConsoleSettings - { - Ansi = AnsiSupport.Detect, - ColorSystem = ColorSystemSupport.Detect, - Out = new AnsiConsoleOutput(writer) - }); - var tree = new Tree(header); - - foreach (var item in items) - { - var formattedItem = itemFormatter?.Invoke(item.Value) ?? item.Value.ToString(); - if (formattedItem is null) - { - continue; - } - var treeNode = tree.AddNode(formattedItem); - AddChildTreeNodes(treeNode, item, itemFormatter); - } - - console.Write(tree); - - return writer.ToString(); - } - - public static string RenderHeader(string header) - { - using var writer = new StringWriter(); - var console = AnsiConsole.Create(new AnsiConsoleSettings - { - Ansi = AnsiSupport.Detect, - ColorSystem = ColorSystemSupport.Detect, - Out = new AnsiConsoleOutput(writer) - }); - - var rule = new Rule(header); - rule.LeftJustified(); - rule.DoubleBorder(); - console.Write(rule); - - return writer.ToString(); - } - - public static string RenderMessage(string message) - { - using var writer = new StringWriter(); - var console = AnsiConsole.Create(new AnsiConsoleSettings - { - Ansi = AnsiSupport.Detect, - ColorSystem = ColorSystemSupport.Detect, - Out = new AnsiConsoleOutput(writer) - }); - - console.Markup(message); - return writer.ToString(); - } - - static void AddChildTreeNodes(TreeNode parent, TreeItem item, Func? itemFormatter = null) where T : notnull - { - foreach (var child in item.Children) - { - var formattedItem = itemFormatter?.Invoke(child.Value) ?? child.Value.ToString(); - if (formattedItem is null) - { - continue; - } - var node = parent.AddNode(formattedItem); - if (child.Children.Count > 0) - { - AddChildTreeNodes(node, child); - } - } - } -} - public class ConsoleDisplayProvider(IAnsiConsole console) : IDisplayProvider { readonly AsyncLocal _currentStatusContext = new(); diff --git a/src/Stack/Infrastructure/ConsoleOutputProvider.cs b/src/Stack/Infrastructure/ConsoleOutputProvider.cs new file mode 100644 index 00000000..a5bf4c31 --- /dev/null +++ b/src/Stack/Infrastructure/ConsoleOutputProvider.cs @@ -0,0 +1,9 @@ +namespace Stack.Infrastructure; + +public class ConsoleOutputProvider : IOutputProvider +{ + public async Task WriteLine(string output, CancellationToken cancellationToken = default) + { + await Console.Out.WriteLineAsync(output.AsMemory(), cancellationToken); + } +} diff --git a/src/Stack/Infrastructure/IDisplayProvider.cs b/src/Stack/Infrastructure/IDisplayProvider.cs new file mode 100644 index 00000000..daa912d2 --- /dev/null +++ b/src/Stack/Infrastructure/IDisplayProvider.cs @@ -0,0 +1,16 @@ +namespace Stack.Infrastructure; + +public interface IDisplayProvider +{ + Task DisplayStatus(string message, Func action, CancellationToken cancellationToken = default); + Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default); + Task DisplaySuccess(string message, CancellationToken cancellationToken = default); + Task DisplayStatusWithSuccess(string message, Func action, CancellationToken cancellationToken = default) + { + return DisplayStatus(message, async ct => + { + await action(ct); + await DisplaySuccess(message, ct); + }, cancellationToken); + } +} diff --git a/src/Stack/Infrastructure/IOutputProvider.cs b/src/Stack/Infrastructure/IOutputProvider.cs new file mode 100644 index 00000000..278c4cb9 --- /dev/null +++ b/src/Stack/Infrastructure/IOutputProvider.cs @@ -0,0 +1,15 @@ +namespace Stack.Infrastructure; + +public interface IOutputProvider +{ + Task WriteLine(string output, CancellationToken cancellationToken); + + Task WriteMessage(string message, CancellationToken cancellationToken) + => WriteLine(RenderingHelpers.RenderMessage(message), cancellationToken); + + Task WriteNewLine(CancellationToken cancellationToken) + => WriteLine(string.Empty, cancellationToken); + + Task WriteHeader(string header, CancellationToken cancellationToken) + => WriteLine(RenderingHelpers.RenderHeader(header), cancellationToken); +} diff --git a/src/Stack/Infrastructure/LoggingDisplayProvider.cs b/src/Stack/Infrastructure/LoggingDisplayProvider.cs new file mode 100644 index 00000000..96b4f64f --- /dev/null +++ b/src/Stack/Infrastructure/LoggingDisplayProvider.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Logging; + +namespace Stack.Infrastructure; + +public class LoggingDisplayProvider(ILogger logger) : IDisplayProvider +{ + public async Task DisplayStatus(string message, Func action, CancellationToken cancellationToken = default) + { + logger.Status(message); + await action(cancellationToken); + } + + public async Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default) + { + logger.Status(message); + return await action(cancellationToken); + } + + public async Task DisplaySuccess(string message, CancellationToken cancellationToken = default) + { + logger.Success(message); + await Task.CompletedTask; + } +} + +public static class KnownEvents +{ + public const int Status = 1; + public const int Success = 2; +} + +public static partial class LoggerExtensionMethods +{ + [LoggerMessage(KnownEvents.Status, LogLevel.Information, "{Message}")] + public static partial void Status(this ILogger logger, string message); + + [LoggerMessage(KnownEvents.Success, LogLevel.Information, "{Message}")] + public static partial void Success(this ILogger logger, string message); +} diff --git a/src/Stack/Infrastructure/RenderingHelpers.cs b/src/Stack/Infrastructure/RenderingHelpers.cs new file mode 100644 index 00000000..7d4769b0 --- /dev/null +++ b/src/Stack/Infrastructure/RenderingHelpers.cs @@ -0,0 +1,83 @@ +using Spectre.Console; + +namespace Stack.Infrastructure; + +public static class RenderingHelpers +{ + public static string RenderTree(string header, IEnumerable> items, Func? itemFormatter = null) + where T : notnull + { + using var writer = new StringWriter(); + var console = AnsiConsole.Create(new AnsiConsoleSettings + { + Ansi = AnsiSupport.Detect, + ColorSystem = ColorSystemSupport.Detect, + Out = new AnsiConsoleOutput(writer) + }); + var tree = new Tree(header); + + foreach (var item in items) + { + var formattedItem = itemFormatter?.Invoke(item.Value) ?? item.Value.ToString(); + if (formattedItem is null) + { + continue; + } + var treeNode = tree.AddNode(formattedItem); + AddChildTreeNodes(treeNode, item, itemFormatter); + } + + console.Write(tree); + + return writer.ToString(); + } + + public static string RenderHeader(string header) + { + using var writer = new StringWriter(); + var console = AnsiConsole.Create(new AnsiConsoleSettings + { + Ansi = AnsiSupport.Detect, + ColorSystem = ColorSystemSupport.Detect, + Out = new AnsiConsoleOutput(writer) + }); + + var rule = new Rule(header); + rule.LeftJustified(); + rule.DoubleBorder(); + console.Write(rule); + + return writer.ToString(); + } + + public static string RenderMessage(string message) + { + using var writer = new StringWriter(); + var console = AnsiConsole.Create(new AnsiConsoleSettings + { + Ansi = AnsiSupport.Detect, + ColorSystem = ColorSystemSupport.Detect, + Out = new AnsiConsoleOutput(writer) + }); + + console.Markup(message); + return writer.ToString(); + } + + static void AddChildTreeNodes(TreeNode parent, TreeItem item, Func? itemFormatter = null) where T : notnull + { + foreach (var child in item.Children) + { + var formattedItem = itemFormatter?.Invoke(child.Value) ?? child.Value.ToString(); + if (formattedItem is null) + { + continue; + } + var node = parent.AddNode(formattedItem); + if (child.Children.Count > 0) + { + AddChildTreeNodes(node, child); + } + } + } +} From ccb9e72cd7d0713352143074d190ba32c8bd9b51 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Wed, 17 Sep 2025 08:52:43 +1000 Subject: [PATCH 11/11] Update json option description and readme --- README.md | 19 +++++++++++++++++-- src/Stack/Infrastructure/CommonOptions.cs | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6a1e02b3..36802432 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -n, --name The name of the stack. Must be unique within the repository. -s, --source-branch The source branch to use for the new stack. Defaults to the default branch for the repository. -b, --branch The name of the branch to create within the stack. @@ -191,7 +192,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. - --json Output results as JSON. + --json Write output and log messages as JSON. Log messages will be written to stderr. -?, -h, --help Show help and usage information ``` @@ -207,7 +208,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. - --json Output results as JSON. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. --all Show status of all stacks. --full Show full status including pull requests. @@ -226,6 +227,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. -y, --yes Confirm the command without prompting. -?, -h, --help Show help and usage information @@ -243,6 +245,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. -n, --name The new name for the stack. -?, -h, --help Show help and usage information @@ -262,6 +265,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. --rebase Use rebase when updating the stack. Overrides any setting in Git configuration. --merge Use merge when updating the stack. Overrides any setting in Git configuration. @@ -280,6 +284,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -b, --branch The name of the branch. -?, -h, --help Show help and usage information ``` @@ -296,6 +301,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. -y, --yes Confirm the command without prompting. -?, -h, --help Show help and usage information @@ -313,6 +319,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. -b, --branch The name of the branch. -p, --parent The name of the parent branch to put the branch under. @@ -331,6 +338,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. -b, --branch The name of the branch. -p, --parent The name of the parent branch to put the branch under. @@ -349,6 +357,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. -b, --branch The name of the branch. -y, --yes Confirm the command without prompting. @@ -371,6 +380,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. -?, -h, --help Show help and usage information ``` @@ -387,6 +397,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. --max-batch-size The maximum number of branches to process at once. [default: 5] --force-with-lease Force push changes with lease. @@ -405,6 +416,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. --max-batch-size The maximum number of branches to process at once. [default: 5] --rebase Use rebase when updating the stack. Overrides any setting in Git configuration. @@ -428,6 +440,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. -?, -h, --help Show help and usage information ``` @@ -444,6 +457,7 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -s, --stack The name of the stack. -?, -h, --help Show help and usage information ``` @@ -462,5 +476,6 @@ Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. --debug Show debug output. --verbose Show verbose output. + --json Write output and log messages as JSON. Log messages will be written to stderr. -?, -h, --help Show help and usage information ``` diff --git a/src/Stack/Infrastructure/CommonOptions.cs b/src/Stack/Infrastructure/CommonOptions.cs index 59339976..b38dbab5 100644 --- a/src/Stack/Infrastructure/CommonOptions.cs +++ b/src/Stack/Infrastructure/CommonOptions.cs @@ -22,7 +22,7 @@ public static class CommonOptions public static Option Json { get; } = new Option("--json") { - Description = "Output results as JSON.", + Description = "Write output and log messages as JSON. Log messages will be written to stderr.", Required = false };