Skip to content

Commit 96e5ca8

Browse files
committed
feat: switch System.CommandLine to Spectre.CLI
1 parent 33c3f79 commit 96e5ca8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3389
-3468
lines changed

src/AWS.Deploy.CLI/AWS.Deploy.CLI.csproj

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<PackageTags>AWS;Amazon;ElasticBeanstalk;ECS;Deploy</PackageTags>
1313
<AssemblyName>AWS.Deploy.CLI</AssemblyName>
1414
<RootNamespace>AWS.Deploy.CLI</RootNamespace>
15+
<LangVersion>Latest</LangVersion>
1516
<PackageIcon>icon.png</PackageIcon>
1617
<PackageProjectUrl>https://github.com/aws/aws-dotnet-deploy</PackageProjectUrl>
1718
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -29,8 +30,9 @@
2930
<PackageReference Include="AWSSDK.SSOOIDC" Version="3.7.301.72" />
3031
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
3132
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
32-
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
3333
<PackageReference Include="System.Text.Json" Version="8.0.5" />
34+
<PackageReference Include="Spectre.Console" Version="0.50.0" />
35+
<PackageReference Include="Spectre.Console.Cli" Version="0.50.0" />
3436
</ItemGroup>
3537

3638
<ItemGroup>

src/AWS.Deploy.CLI/App.cs

+77-77
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,96 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
using System;
5-
using System.CommandLine;
6-
using System.Reflection;
7-
using System.Text;
84
using System.Threading.Tasks;
95
using AWS.Deploy.CLI.Commands;
6+
using AWS.Deploy.CLI.Utilities;
7+
using AWS.Deploy.Common;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Spectre.Console.Cli;
1010

11-
namespace AWS.Deploy.CLI
11+
namespace AWS.Deploy.CLI;
12+
13+
public class App
1214
{
13-
public class App
15+
public static CommandApp<RootCommand> ConfigureServices(TypeRegistrar registrar)
1416
{
15-
private readonly ICommandFactory _commandFactory;
16-
private readonly IToolInteractiveService _toolInteractiveService;
17-
18-
public App(ICommandFactory commandFactory, IToolInteractiveService toolInteractiveService)
19-
{
20-
_commandFactory = commandFactory;
21-
_toolInteractiveService = toolInteractiveService;
22-
}
17+
var app = new CommandApp<RootCommand>(registrar);
2318

24-
public async Task<int> Run(string[] args)
19+
app.Configure(config =>
2520
{
26-
Console.OutputEncoding = Encoding.UTF8;
27-
28-
SetExecutionEnvironment(args);
29-
30-
_toolInteractiveService.WriteLine("AWS .NET deployment tool for deploying .NET Core applications to AWS.");
31-
_toolInteractiveService.WriteLine("Project Home: https://github.com/aws/aws-dotnet-deploy");
32-
_toolInteractiveService.WriteLine(string.Empty);
33-
34-
// if user didn't specify a command, default to help
35-
if (args.Length == 0)
21+
config.SetApplicationName(Constants.CLI.TOOL_NAME);
22+
config.AddCommand<DeployCommand>("deploy")
23+
.WithDescription("Inspect, build, and deploy the .NET project to AWS using the recommended AWS service.");
24+
config.AddCommand<ListDeploymentsCommand>("list-deployments")
25+
.WithDescription("List existing deployments.");
26+
config.AddCommand<DeleteDeploymentCommand>("delete-deployment")
27+
.WithDescription("Delete an existing deployment.");
28+
config.AddBranch("deployment-project", deploymentProject =>
3629
{
37-
args = new[] { "-h" };
38-
}
39-
40-
return await _commandFactory.BuildRootCommand().InvokeAsync(args);
41-
}
42-
43-
/// <summary>
44-
/// Set up the execution environment variable picked up by the AWS .NET SDK. This can be useful for identify calls
45-
/// made by this tool in AWS CloudTrail.
46-
/// </summary>
47-
private static void SetExecutionEnvironment(string[] args)
48-
{
49-
const string envName = "AWS_EXECUTION_ENV";
50-
51-
var toolVersion = GetToolVersion();
52-
53-
// The leading and trailing whitespaces are intentional
54-
var userAgent = $" lib/aws-dotnet-deploy-cli#{toolVersion} ";
55-
if (args?.Length > 0)
30+
deploymentProject.SetDescription("Save the deployment project inside a user provided directory path.");
31+
deploymentProject.AddCommand<GenerateDeploymentProjectCommand>("generate")
32+
.WithDescription("Save the deployment project inside a user provided directory path without proceeding with a deployment");
33+
});
34+
config.AddCommand<ServerModeCommand>("server-mode")
35+
.WithDescription("Launches the tool in a server mode for IDEs like Visual Studio to integrate with.");
36+
37+
config.SetExceptionHandler((exception, _) =>
5638
{
57-
// The trailing whitespace is intentional
58-
userAgent = $"{userAgent}md/cli-args#{args[0]} ";
59-
}
60-
61-
62-
var envValue = new StringBuilder();
63-
var existingValue = Environment.GetEnvironmentVariable(envName);
64-
65-
// If there is an existing execution environment variable add this tool as a suffix.
66-
if (!string.IsNullOrEmpty(existingValue))
67-
{
68-
envValue.Append(existingValue);
69-
}
39+
var serviceProvider = registrar.GetServiceProvider();;
40+
var toolInteractiveService = serviceProvider.GetRequiredService<IToolInteractiveService>();
41+
42+
if (exception.IsAWSDeploymentExpectedException())
43+
{
44+
if (toolInteractiveService.Diagnostics)
45+
toolInteractiveService.WriteErrorLine(exception.PrettyPrint());
46+
else
47+
{
48+
toolInteractiveService.WriteErrorLine(string.Empty);
49+
toolInteractiveService.WriteErrorLine(exception.Message);
50+
}
51+
52+
toolInteractiveService.WriteErrorLine(string.Empty);
53+
toolInteractiveService.WriteErrorLine("For more information, please visit our troubleshooting guide https://aws.github.io/aws-dotnet-deploy/troubleshooting-guide/.");
54+
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.");
55+
56+
if (exception is TcpPortInUseException)
57+
{
58+
return CommandReturnCodes.TCP_PORT_ERROR;
59+
}
60+
61+
// bail out with an non-zero return code.
62+
return CommandReturnCodes.USER_ERROR;
63+
}
64+
else
65+
{
66+
// This is a bug
67+
toolInteractiveService.WriteErrorLine(
68+
"Unhandled exception. This is a bug. Please copy the stack trace below and file a bug at https://github.com/aws/aws-dotnet-deploy. " +
69+
exception.PrettyPrint());
70+
71+
return CommandReturnCodes.UNHANDLED_EXCEPTION;
72+
}
73+
});
74+
});
75+
76+
return app;
77+
}
7078

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

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

76-
internal static string GetToolVersion()
88+
// if user didn't specify a command, default to help
89+
if (args.Length == 0)
7790
{
78-
var assembly = typeof(App).GetTypeInfo().Assembly;
79-
var version = assembly.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version;
80-
if (version is null)
81-
{
82-
return string.Empty;
83-
}
84-
85-
var versionParts = version.Split('.');
86-
if (versionParts.Length == 4)
87-
{
88-
// The revision part of the version number is intentionally set to 0 since package versioning on
89-
// NuGet follows semantic versioning consisting only of Major.Minor.Patch versions.
90-
versionParts[3] = "0";
91-
}
92-
93-
return string.Join(".", versionParts);
91+
args = ["-h"];
9492
}
93+
94+
return await app.RunAsync(args);
9595
}
9696
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System.Runtime.InteropServices;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace Spectre.Console.Cli;
9+
10+
/// <summary>
11+
/// Provides an abstract base class for asynchronous commands that support cancellation.
12+
/// </summary>
13+
/// <typeparam name="TSettings">The type of the settings used for the command.</typeparam>
14+
public abstract class CancellableAsyncCommand<TSettings> : AsyncCommand<TSettings> where TSettings : CommandSettings
15+
{
16+
/// <summary>
17+
/// Executes the command asynchronously, with support for cancellation.
18+
/// </summary>
19+
public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings, CancellationTokenSource cancellationTokenSource);
20+
21+
/// <summary>
22+
/// Executes the command asynchronously with built-in cancellation handling.
23+
/// </summary>
24+
public sealed override async Task<int> ExecuteAsync(CommandContext context, TSettings settings)
25+
{
26+
using var cancellationSource = new CancellationTokenSource();
27+
28+
using var sigInt = PosixSignalRegistration.Create(PosixSignal.SIGINT, onSignal);
29+
using var sigQuit = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, onSignal);
30+
using var sigTerm = PosixSignalRegistration.Create(PosixSignal.SIGTERM, onSignal);
31+
32+
var cancellable = ExecuteAsync(context, settings, cancellationSource);
33+
return await cancellable;
34+
35+
void onSignal(PosixSignalContext context)
36+
{
37+
context.Cancel = true;
38+
cancellationSource.Cancel();
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)