Skip to content

Switch System.CommandLine to Spectre.CLI #920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: feature/net8-upgrade
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/AWS.Deploy.CLI/AWS.Deploy.CLI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<PackageTags>AWS;Amazon;ElasticBeanstalk;ECS;Deploy</PackageTags>
<AssemblyName>AWS.Deploy.CLI</AssemblyName>
<RootNamespace>AWS.Deploy.CLI</RootNamespace>
<LangVersion>Latest</LangVersion>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://github.com/aws/aws-dotnet-deploy</PackageProjectUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand All @@ -29,8 +30,9 @@
<PackageReference Include="AWSSDK.SSOOIDC" Version="3.7.301.72" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="Spectre.Console" Version="0.50.0" />
<PackageReference Include="Spectre.Console.Cli" Version="0.50.0" />
</ItemGroup>

<ItemGroup>
Expand Down
154 changes: 77 additions & 77 deletions src/AWS.Deploy.CLI/App.cs
Original file line number Diff line number Diff line change
@@ -1,96 +1,96 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.CommandLine;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using AWS.Deploy.CLI.Commands;
using AWS.Deploy.CLI.Utilities;
using AWS.Deploy.Common;
using Microsoft.Extensions.DependencyInjection;
using Spectre.Console.Cli;

namespace AWS.Deploy.CLI
namespace AWS.Deploy.CLI;

public class App
{
public class App
public static CommandApp<RootCommand> ConfigureServices(TypeRegistrar registrar)
{
private readonly ICommandFactory _commandFactory;
private readonly IToolInteractiveService _toolInteractiveService;

public App(ICommandFactory commandFactory, IToolInteractiveService toolInteractiveService)
{
_commandFactory = commandFactory;
_toolInteractiveService = toolInteractiveService;
}
var app = new CommandApp<RootCommand>(registrar);

public async Task<int> Run(string[] args)
app.Configure(config =>
{
Console.OutputEncoding = Encoding.UTF8;

SetExecutionEnvironment(args);

_toolInteractiveService.WriteLine("AWS .NET deployment tool for deploying .NET Core applications to AWS.");
_toolInteractiveService.WriteLine("Project Home: https://github.com/aws/aws-dotnet-deploy");
_toolInteractiveService.WriteLine(string.Empty);

// if user didn't specify a command, default to help
if (args.Length == 0)
config.SetApplicationName(Constants.CLI.TOOL_NAME);
config.AddCommand<DeployCommand>("deploy")
.WithDescription("Inspect, build, and deploy the .NET project to AWS using the recommended AWS service.");
config.AddCommand<ListDeploymentsCommand>("list-deployments")
.WithDescription("List existing deployments.");
config.AddCommand<DeleteDeploymentCommand>("delete-deployment")
.WithDescription("Delete an existing deployment.");
config.AddBranch("deployment-project", deploymentProject =>
{
args = new[] { "-h" };
}

return await _commandFactory.BuildRootCommand().InvokeAsync(args);
}

/// <summary>
/// Set up the execution environment variable picked up by the AWS .NET SDK. This can be useful for identify calls
/// made by this tool in AWS CloudTrail.
/// </summary>
private static void SetExecutionEnvironment(string[] args)
{
const string envName = "AWS_EXECUTION_ENV";

var toolVersion = GetToolVersion();

// The leading and trailing whitespaces are intentional
var userAgent = $" lib/aws-dotnet-deploy-cli#{toolVersion} ";
if (args?.Length > 0)
deploymentProject.SetDescription("Save the deployment project inside a user provided directory path.");
deploymentProject.AddCommand<GenerateDeploymentProjectCommand>("generate")
.WithDescription("Save the deployment project inside a user provided directory path without proceeding with a deployment");
});
config.AddCommand<ServerModeCommand>("server-mode")
.WithDescription("Launches the tool in a server mode for IDEs like Visual Studio to integrate with.");

config.SetExceptionHandler((exception, _) =>
{
// The trailing whitespace is intentional
userAgent = $"{userAgent}md/cli-args#{args[0]} ";
}


var envValue = new StringBuilder();
var existingValue = Environment.GetEnvironmentVariable(envName);

// If there is an existing execution environment variable add this tool as a suffix.
if (!string.IsNullOrEmpty(existingValue))
{
envValue.Append(existingValue);
}
var serviceProvider = registrar.GetServiceProvider();;
var toolInteractiveService = serviceProvider.GetRequiredService<IToolInteractiveService>();

if (exception.IsAWSDeploymentExpectedException())
{
if (toolInteractiveService.Diagnostics)
toolInteractiveService.WriteErrorLine(exception.PrettyPrint());
else
{
toolInteractiveService.WriteErrorLine(string.Empty);
toolInteractiveService.WriteErrorLine(exception.Message);

Check warning on line 49 in src/AWS.Deploy.CLI/App.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.CLI/App.cs#L48-L49

Added lines #L48 - L49 were not covered by tests
}

toolInteractiveService.WriteErrorLine(string.Empty);
toolInteractiveService.WriteErrorLine("For more information, please visit our troubleshooting guide https://aws.github.io/aws-dotnet-deploy/troubleshooting-guide/.");
toolInteractiveService.WriteErrorLine("If you are still unable to solve this issue and believe this is an issue with the tooling, please cut a ticket https://github.com/aws/aws-dotnet-deploy/issues/new/choose.");

if (exception is TcpPortInUseException)
{
return CommandReturnCodes.TCP_PORT_ERROR;

Check warning on line 58 in src/AWS.Deploy.CLI/App.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.CLI/App.cs#L58

Added line #L58 was not covered by tests
}

// bail out with an non-zero return code.
return CommandReturnCodes.USER_ERROR;
}
else
{
// This is a bug
toolInteractiveService.WriteErrorLine(
"Unhandled exception. This is a bug. Please copy the stack trace below and file a bug at https://github.com/aws/aws-dotnet-deploy. " +
exception.PrettyPrint());

return CommandReturnCodes.UNHANDLED_EXCEPTION;
}
});
});

return app;
}

envValue.Append(userAgent);
public static async Task<int> RunAsync(string[] args, CommandApp<RootCommand> app, TypeRegistrar registrar)
{
var serviceProvider = registrar.GetServiceProvider();;
var toolInteractiveService = serviceProvider.GetRequiredService<IToolInteractiveService>();

Environment.SetEnvironmentVariable(envName, envValue.ToString());
}
toolInteractiveService.WriteLine("AWS .NET deployment tool for deploying .NET Core applications to AWS.");
toolInteractiveService.WriteLine("Project Home: https://github.com/aws/aws-dotnet-deploy");
toolInteractiveService.WriteLine(string.Empty);

internal static string GetToolVersion()
// if user didn't specify a command, default to help
if (args.Length == 0)
{
var assembly = typeof(App).GetTypeInfo().Assembly;
var version = assembly.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version;
if (version is null)
{
return string.Empty;
}

var versionParts = version.Split('.');
if (versionParts.Length == 4)
{
// The revision part of the version number is intentionally set to 0 since package versioning on
// NuGet follows semantic versioning consisting only of Major.Minor.Patch versions.
versionParts[3] = "0";
}

return string.Join(".", versionParts);
args = ["-h"];

Check warning on line 91 in src/AWS.Deploy.CLI/App.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.CLI/App.cs#L91

Added line #L91 was not covered by tests
}

return await app.RunAsync(args);
}
}
41 changes: 41 additions & 0 deletions src/AWS.Deploy.CLI/Commands/CancellableAsyncCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0

using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Spectre.Console.Cli;

/// <summary>
/// Provides an abstract base class for asynchronous commands that support cancellation.
/// </summary>
/// <typeparam name="TSettings">The type of the settings used for the command.</typeparam>
public abstract class CancellableAsyncCommand<TSettings> : AsyncCommand<TSettings> where TSettings : CommandSettings
{
/// <summary>
/// Executes the command asynchronously, with support for cancellation.
/// </summary>
public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings, CancellationTokenSource cancellationTokenSource);

/// <summary>
/// Executes the command asynchronously with built-in cancellation handling.
/// </summary>
public sealed override async Task<int> ExecuteAsync(CommandContext context, TSettings settings)
{
using var cancellationSource = new CancellationTokenSource();

using var sigInt = PosixSignalRegistration.Create(PosixSignal.SIGINT, onSignal);
using var sigQuit = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, onSignal);
using var sigTerm = PosixSignalRegistration.Create(PosixSignal.SIGTERM, onSignal);

var cancellable = ExecuteAsync(context, settings, cancellationSource);
return await cancellable;

void onSignal(PosixSignalContext context)
{
context.Cancel = true;
cancellationSource.Cancel();
}

Check warning on line 39 in src/AWS.Deploy.CLI/Commands/CancellableAsyncCommand.cs

View check run for this annotation

Codecov / codecov/patch

src/AWS.Deploy.CLI/Commands/CancellableAsyncCommand.cs#L37-L39

Added lines #L37 - L39 were not covered by tests
}
}
Loading
Loading