Skip to content

Commit

Permalink
Using System.CommandLine instead of the out-of-maintaince package (#2115
Browse files Browse the repository at this point in the history
)

* Using System.CommandLine instead of the out-of-maintaince package

* Downgrade Xunit package version.

* Fix test failure
  • Loading branch information
vicancy authored Dec 10, 2024
1 parent 979f193 commit f7ae346
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 175 deletions.
4 changes: 2 additions & 2 deletions build/dependencies.private.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<MicrosoftExtensionsLoggingPackageVersion>8.0.0</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftNETTestSdkPackageVersion>17.11.0</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.20.70</MoqPackageVersion>
<XunitPackageVersion>2.4.2</XunitPackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.4.2</XunitRunnerVisualStudioPackageVersion>
<XunitPackageVersion>2.8.1</XunitPackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.8.2</XunitRunnerVisualStudioPackageVersion>
<MicrosoftOwinTestingPackageVersion>4.2.2</MicrosoftOwinTestingPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>8.0.8</MicrosoftAspNetCoreTestHostPackageVersion>
<SystemThreadingTasksExtensionsVersion>4.5.4</SystemThreadingTasksExtensionsVersion>
Expand Down
2 changes: 1 addition & 1 deletion build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<MicrosoftSourceLinkGitHubPackageVersion>1.1.1</MicrosoftSourceLinkGitHubPackageVersion>

<!--Emulator, self-contained, always try the latest version -->
<MicrosoftExtensionsCommandLineUtilsPackageVersion>1.1.1</MicrosoftExtensionsCommandLineUtilsPackageVersion>
<SystemCommandLinePackageVersion>2.0.0-beta4.22272.1</SystemCommandLinePackageVersion>
<EmulatorMicrosoftPackageVersion>8.0.11</EmulatorMicrosoftPackageVersion>

<!-- Vulnerability fix-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="$(EmulatorMicrosoftPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="$(EmulatorMicrosoftPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(EmulatorMicrosoftPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="$(MicrosoftExtensionsCommandLineUtilsPackageVersion)" />
<PackageReference Include="System.CommandLine" Version="$(SystemCommandLinePackageVersion)" />
<PackageReference Include="MessagePack" Version="$(MessagePackPackageVersion)" />
<PackageReference Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" />
<PackageReference Include="System.Text.RegularExpressions" Version="$(SystemTextRegularExpressionsPackageVersion)" />
</ItemGroup>
</Project>
271 changes: 141 additions & 130 deletions src/Microsoft.Azure.SignalR.Emulator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.CommandLine;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

#nullable enable
namespace Microsoft.Azure.SignalR.Emulator
{
public class Program
Expand All @@ -19,189 +22,199 @@ public class Program
private static readonly string SettingsFileName = "settings.json";
private static readonly string SettingsFile = Path.GetFullPath(SettingsFileName);
private static readonly string AppSettingsFile = Path.Combine(AppContext.BaseDirectory, "appsettings.json");

internal static readonly string ProgramDefaultSettingsFile = Path.Combine(AppContext.BaseDirectory, SettingsFileName);

public static void Main(string[] args)
public static async Task<int> Main(string[] args)
{
var app = new CommandLineApplication();
app.Name = "asrs-emulator";
app.Description = "The local emulator for Azure SignalR Serverless features.";
app.HelpOption("-h|--help");

app.Command("upstream", command =>
var rootCommand = new RootCommand("The local emulator for Azure SignalR Serverless features.")
{
command.Description = "To init/list the upstream options";
command.HelpOption("-h|--help");
command.Command("init", c =>
{
c.Description = "Init the default upstream options into a settings.json config. Use -o to specify the folder to export the default settings.";
var configOptions = c.Option("-o|--output", "Specify the folder to init the upstream settings file.", CommandOptionType.SingleValue);
c.HelpOption("-h|--help");
c.OnExecute(() =>
{
string outputFile = configOptions.HasValue() ? Path.GetFullPath(Path.Combine(configOptions.Value(), SettingsFileName)) : SettingsFile;
if (File.Exists(outputFile))
{
Console.WriteLine($"Already contains '{outputFile}', still want to override it with the default one? (N/y)");
if (Console.ReadKey().Key != ConsoleKey.Y)
{
return 0;
}
Name = "asrs-emulator"
};

Console.WriteLine();
}
rootCommand.AddCommand(CreateUpstreamCommand());
rootCommand.AddCommand(CreateStartCommand());

Directory.CreateDirectory(Path.GetDirectoryName(outputFile));
File.Copy(ProgramDefaultSettingsFile, outputFile, true);
return await rootCommand.InvokeAsync(args);
}

Console.WriteLine($"Exported default settings to '{outputFile}'.");
return 0;
});
});
command.Command("list", c =>
{
c.Description = "List current upstream options. Use -c to specify the folder or file to read the settings.";
var configOptions = c.Option("-c|--config", "Specify the upstream settings file to load from.", CommandOptionType.SingleValue);
c.HelpOption("-h|--help");
c.OnExecute(() =>
{
if (!TryGetConfigFilePath(configOptions, out var config))
{
return 1;
}
private static Command CreateUpstreamCommand()
{
var upstreamCommand = new Command("upstream", "To init/list the upstream options")
{
CreateInitCommand(),
CreateListCommand()
};

var host = CreateHostBuilder(args, null, DefaultPort, config).Build();

Console.WriteLine($"Loaded upstream settings from '{config}'");

var option = host.Services.GetRequiredService<IOptions<UpstreamOptions>>();
option.Value.Print();
return 0;
});
});
});
return upstreamCommand;
}

app.Command("start", command =>
private static Command CreateInitCommand()
{
var outputOption = new Option<string?>(
new[] { "-o", "--output" },
"Specify the folder to init the upstream settings file."
);

var initCommand = new Command("init", "Init the default upstream options into a settings.json config")
{
command.Description = "To start the emulator.";
var portOptions = command.Option("-p|--port", "Specify the port to use.", CommandOptionType.SingleValue);
var ipOptions = command.Option("-i|--ip", "Specify the IP address to use.", CommandOptionType.SingleValue);
var configOptions = command.Option("-c|--config", "Specify the upstream settings file to load from.", CommandOptionType.SingleValue);
command.HelpOption("-h|--help");
command.OnExecute(() =>
outputOption
};

initCommand.SetHandler((string? output) =>
{
string outputFile = !string.IsNullOrEmpty(output)
? Path.GetFullPath(Path.Combine(output, SettingsFileName))
: SettingsFile;

if (File.Exists(outputFile))
{
if (!TryGetPort(portOptions, out var port) || !TryGetConfigFilePath(configOptions, out var config) || !TryGetIpAddress(ipOptions, out var ip))
Console.WriteLine($"Already contains '{outputFile}', still want to override it with the default one? (N/y)");
if (Console.ReadKey().Key != ConsoleKey.Y)
{
return 1;
return;
}

Console.WriteLine($"Loaded settings from '{config}'. Changes to the settings file will be hot-loaded into the emulator.");
Console.WriteLine();
}

CreateHostBuilder(args, ip, port, config).Build().Run();
return 0;
});
});
Directory.CreateDirectory(Path.GetDirectoryName(outputFile)!);
File.Copy(ProgramDefaultSettingsFile, outputFile, true);

app.OnExecute(() =>
Console.WriteLine($"Exported default settings to '{outputFile}'.");
}, outputOption);

return initCommand;
}

private static Command CreateListCommand()
{
var configOption = new Option<string?>(
new[] { "-c", "--config" },
"Specify the upstream settings file to load from."
);

var listCommand = new Command("list", "List current upstream options")
{
app.ShowHelp();
return 0;
});
configOption
};

try
listCommand.SetHandler((string? config) =>
{
app.Execute(args);
}
catch (Exception e)
if (!TryGetConfigFilePath(config, out var configFile))
{
return;
}

var host = CreateHostBuilder(null, null, DefaultPort, configFile).Build();

Console.WriteLine($"Loaded upstream settings from '{configFile}'");

var options = host.Services.GetRequiredService<IOptions<UpstreamOptions>>();
options.Value.Print();
}, configOption);

return listCommand;
}

private static Command CreateStartCommand()
{
var portOption = new Option<int?>(
new[] { "-p", "--port" },
() => DefaultPort,
"Specify the port to use."
);
var ipOption = new Option<string?>(
new[] { "-i", "--ip" },
"Specify the IP address to use."
);
var configOption = new Option<string?>(
new[] { "-c", "--config" },
"Specify the upstream settings file to load from."
);

var startCommand = new Command("start", "To start the emulator.")
{
Console.WriteLine($"Error starting emulator: {e.Message}.");
}
portOption,
ipOption,
configOption
};

startCommand.SetHandler((int? port, string? ip, string? config) =>
{
if (!TryGetPort(port, out var actualPort) ||
!TryGetConfigFilePath(config, out var configFile) ||
!TryGetIpAddress(ip, out var actualIp))
{
return;
}

Console.WriteLine($"Loaded settings from '{configFile}'. Changes to the settings file will be hot-loaded into the emulator.");

CreateHostBuilder(null, actualIp, actualPort, configFile).Build().Run();
}, portOption, ipOption, configOption);

return startCommand;
}

public static IHostBuilder CreateHostBuilder(string[] args, IPAddress ip, int port, string configFile)
private static IHostBuilder CreateHostBuilder(string[]? args, IPAddress? ip, int port, string configFile)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseKestrel(o =>
webBuilder.UseKestrel(options =>
{
if (ip == null)
{
o.ListenLocalhost(port);
options.ListenLocalhost(port);
}
else
{
o.Listen(ip, port);
options.Listen(ip, port);
}
});
})
.ConfigureAppConfiguration(s =>
.ConfigureAppConfiguration(config =>
{
s.AddJsonFile(AppSettingsFile, optional: true, reloadOnChange: true);
s.AddJsonFile(configFile, optional: true, reloadOnChange: true);
config.AddJsonFile(AppSettingsFile, optional: true, reloadOnChange: true);
config.AddJsonFile(configFile, optional: true, reloadOnChange: true);
});
}

private static bool TryGetIpAddress(CommandOption ipOptions, out IPAddress ip)
private static bool TryGetIpAddress(string? ipOption, out IPAddress? ip)
{
if (ipOptions.HasValue())
if (!string.IsNullOrEmpty(ipOption))
{
var val = ipOptions.Value();

if (IPAddress.TryParse(val, out ip))
if (IPAddress.TryParse(ipOption, out ip))
{
return true;
}
else
{
Console.WriteLine($"Invalid IP address value: {val}");
return false;
}
}
else
{
ip = null;
return true;

Console.WriteLine($"Invalid IP address value: {ipOption}");
return false;
}

ip = null;
return true;
}

private static bool TryGetPort(CommandOption portOption, out int port)
private static bool TryGetPort(int? portOption, out int port)
{
if (portOption.HasValue())
{
var val = portOption.Value();

if( int.TryParse(val, out port))
{
return true;
}
else
{
Console.WriteLine($"Invalid port value: {val}");
return false;
}
}
else
{
port = DefaultPort;
return true;
}
port = portOption ?? DefaultPort;
return true;
}

private static bool TryGetConfigFilePath(CommandOption configOption, out string path)
private static bool TryGetConfigFilePath(string? configOption, [NotNullWhen(true)]out string? path)
{
if (configOption.HasValue())
if (!string.IsNullOrEmpty(configOption))
{
var fileAttempt = Path.GetFullPath(configOption.Value());
var fileAttempt = Path.GetFullPath(configOption);
if (File.Exists(fileAttempt))
{
path = fileAttempt;
return true;
}

// Try this as a folder
var folderAttempt = Path.GetFullPath(Path.Combine(fileAttempt, SettingsFileName));
if (File.Exists(folderAttempt))
{
Expand All @@ -213,11 +226,9 @@ private static bool TryGetConfigFilePath(CommandOption configOption, out string
path = null;
return false;
}
else
{
path = SettingsFile;
return true;
}

path = SettingsFile;
return true;
}
}
}
2 changes: 0 additions & 2 deletions test/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MessagePack" Version="$(MessagePackPackageVersion)" />
<PackageReference Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" />
<PackageReference Include="System.Text.RegularExpressions" Version="$(SystemTextRegularExpressionsPackageVersion)" />
</ItemGroup>
</Project>
Loading

0 comments on commit f7ae346

Please sign in to comment.