diff --git a/MSStore.API/MSStore.API.csproj b/MSStore.API/MSStore.API.csproj index 979416e..d49d4b7 100644 --- a/MSStore.API/MSStore.API.csproj +++ b/MSStore.API/MSStore.API.csproj @@ -21,9 +21,9 @@ - - - + + + diff --git a/MSStore.CLI.UnitTests/BaseCommandLineTest.cs b/MSStore.CLI.UnitTests/BaseCommandLineTest.cs index b845360..06f7194 100644 --- a/MSStore.CLI.UnitTests/BaseCommandLineTest.cs +++ b/MSStore.CLI.UnitTests/BaseCommandLineTest.cs @@ -2,11 +2,7 @@ // Licensed under the MIT License. using System.CommandLine; -using System.CommandLine.Builder; -using System.CommandLine.Hosting; using System.CommandLine.Invocation; -using System.CommandLine.IO; -using System.CommandLine.Parsing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -35,7 +31,6 @@ namespace MSStore.CLI.UnitTests { public class BaseCommandLineTest { - internal MicrosoftStoreCLI Cli { get; private set; } = null!; internal Mock FakeConsole { get; private set; } = null!; internal Mock> FakeConfigurationManager { get; private set; } = null!; internal Mock> FakeTelemetryConfigurationManager { get; private set; } = null!; @@ -97,7 +92,7 @@ public class BaseCommandLineTest Id = new Guid("F3C1CCB6-09C0-4BAB-BABA-C034BFB60EF9") }; - private Parser _parser = null!; + private IHostBuilder _hostBuilder = null!; protected IAnsiConsole ErrorAnsiConsole { get; private set; } = null!; protected static string CopyFilesRecursively(string sourcePath, [CallerMemberName] string caller = null!) @@ -204,12 +199,8 @@ public void Initialize() .Setup(fac => fac.CreatePackagedAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(FakeStorePackagedAPI.Object); - Cli = []; - StorePackagedAPI.DefaultSubmissionPollDelay = TimeSpan.Zero; - Cli.AddCommand(new TestCommand(this)); - var azureBlobManagerMock = new Mock(); azureBlobManagerMock .Setup(x => x.UploadFileAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) @@ -242,8 +233,7 @@ public void Initialize() TokenManager = new Mock(); - var builder = new CommandLineBuilder(Cli); - _parser = builder.UseHost(_ => Host.CreateDefaultBuilder(null), (builder) => builder + _hostBuilder = Host.CreateDefaultBuilder(null) .UseEnvironment("CLI") .ConfigureServices((hostContext, services) => { @@ -279,8 +269,7 @@ public void Initialize() .AddScoped(sp => PWAAppInfoManager.Object) .AddScoped(sp => ElectronManifestManager.Object) .AddScoped(sp => NuGetPackageManager.Object) - .AddScoped(sp => AppXManifestManager.Object) - .AddSingleton(Cli); + .AddScoped(sp => AppXManifestManager.Object); services.AddLogging(builder => { @@ -292,39 +281,7 @@ public void Initialize() .ConfigureLogging((hostContext, logging) => { logging.SetMinimumLevel(LogLevel.Debug); - })) - .AddMiddleware( - async (context, next) => - { - var ct = context.GetCancellationToken(); - - var host = context.GetHost(); - - var configurationManager = host.Services.GetService>()!; - var credentialManager = host.Services.GetService()!; - var consoleReader = host.Services.GetService()!; - var cliConfigurator = host.Services.GetService()!; - var logger = host.Services.GetService>()!; - - if (context.ParseResult.CommandResult.Command is MicrosoftStoreCLI - || context.ParseResult.CommandResult.Command is ReconfigureCommand - || context.ParseResult.CommandResult.Command is TestCommand - || await MicrosoftStoreCLI.InitAsync(ErrorAnsiConsole, configurationManager, credentialManager, consoleReader, cliConfigurator, logger, ct)) - { - await next(context).ConfigureAwait(false); - } - }, MiddlewareOrder.Default) - .UseVersionOption() - .UseEnvironmentVariableDirective() - .UseParseDirective() - .UseSuggestDirective() - .RegisterWithDotnetSuggest() - .UseTypoCorrections() - .UseParseErrorReporting() - .UseExceptionHandler() - .UseHelp() - .CancelOnProcessTermination() - .Build(); + }); } protected void FakeLogin(string? publisherDisplayName = null) @@ -792,7 +749,7 @@ protected void SetupBasedOnTestDataProjectSubPath(DirectoryInfo dirInfo, string[ } } - protected Task<(string Output, string Error)> RunTestAsync(Func? testCallback) + protected Task<(string Output, string Error)> RunTestAsync(Func? testCallback) { _testCallback = testCallback; @@ -818,19 +775,39 @@ protected void SetupBasedOnTestDataProjectSubPath(DirectoryInfo dirInfo, string[ AnsiConsole.Profile.Capabilities.Ansi = true; AnsiConsole.Profile.Capabilities.Unicode = true; - var parseResult = _parser.Parse(args); + IHost host = _hostBuilder.Start(); - if (parseResult.Errors.Any()) + var storeCLI = host.Services.GetRequiredService(); + storeCLI.Subcommands.Add(new TestCommand(this, host)); + var parseResult = storeCLI.Parse(args); + + IHostApplicationLifetime lifetime = host.Services.GetRequiredService(); + + if (parseResult.CommandResult.Command is not MicrosoftStoreCLI + && parseResult.CommandResult.Command is not ReconfigureCommand + && parseResult.CommandResult.Command is not TestCommand + && !await MicrosoftStoreCLI.InitAsync(ErrorAnsiConsole, host.Services.GetService>()!, host.Services.GetService()!, host.Services.GetService()!, host.Services.GetService()!, host.Services.GetService>()!, lifetime.ApplicationStopping)) + { + // Initialization failed + await host.StopAsync(); + return (Output: string.Empty, Error: string.Empty); + } + + if (parseResult.Action is ParseErrorAction parseError) { - throw new ArgumentException(string.Join(Environment.NewLine, parseResult.Errors.Select(e => e.Message))); + parseError.ShowTypoCorrections = true; + parseError.ShowHelp = true; } - var testOutputConsole = new TestConsole(outputCapture, errorCapture); + parseResult.InvocationConfiguration.Output = outputCapture; + parseResult.InvocationConfiguration.Error = errorCapture; - var invokeTask = parseResult.InvokeAsync(testOutputConsole); + var invokeTask = parseResult.InvokeAsync(parseResult.InvocationConfiguration); var result = await invokeTask.ConfigureAwait(false); + await host.StopAsync(); + if (expectedResult.HasValue) { result.Should().Be(expectedResult.Value); @@ -858,13 +835,13 @@ private OutputCapture RefreshAnsiConsole() return errorCapture; } - private Func? _testCallback; + private Func? _testCallback; - private async Task TestAsync(InvocationContext invocationContext) + private async Task TestAsync(ParseResult parseResult, IHost host, CancellationToken cancellationToken) { if (_testCallback != null) { - await _testCallback(invocationContext); + await _testCallback(parseResult, host, cancellationToken); } } @@ -872,15 +849,15 @@ private sealed class TestCommand : Command { private readonly BaseCommandLineTest _baseCommandLineTest; - public TestCommand(BaseCommandLineTest baseCommandLineTest) + public TestCommand(BaseCommandLineTest baseCommandLineTest, IHost host) : base("test") { _baseCommandLineTest = baseCommandLineTest; - this.SetHandler(_baseCommandLineTest.TestAsync); + SetAction((parseResult, ct) => _baseCommandLineTest.TestAsync(parseResult, host, ct)); } } - internal sealed class OutputCapture : TextWriter, IStandardStreamWriter, IDisposable + internal sealed class OutputCapture : TextWriter, IDisposable { #pragma warning disable CA2213 // Disposable fields should be disposed private readonly TextWriter _stdOutWriter; @@ -908,15 +885,6 @@ public override void WriteLine(string? value) } } - internal sealed class TestConsole(BaseCommandLineTest.OutputCapture outputCapture, BaseCommandLineTest.OutputCapture errorCapture) : IConsole - { - public IStandardStreamWriter Error { get; set; } = errorCapture; - public IStandardStreamWriter Out { get; set; } = outputCapture; - public bool IsOutputRedirected { get; set; } - public bool IsErrorRedirected { get; set; } - public bool IsInputRedirected { get; set; } - } - internal sealed class CustomAnsiConsoleOutput(TextWriter writer) : IAnsiConsoleOutput { public TextWriter Writer { get; } = writer ?? throw new ArgumentNullException(nameof(writer)); diff --git a/MSStore.CLI.UnitTests/EmptyCommandUnitTests.cs b/MSStore.CLI.UnitTests/EmptyCommandUnitTests.cs index 2d89731..34409f9 100644 --- a/MSStore.CLI.UnitTests/EmptyCommandUnitTests.cs +++ b/MSStore.CLI.UnitTests/EmptyCommandUnitTests.cs @@ -13,7 +13,7 @@ public async Task EmptyCommandShouldReturnZeroIfLoggedIn() var result = await ParseAndInvokeAsync([]); - result.Error.Should().Contain("CLI tool to automate Microsoft Store Developer tasks."); + result.Output.Should().Contain("CLI tool to automate Microsoft Store Developer tasks."); } [TestMethod] diff --git a/MSStore.CLI.UnitTests/FlightsSubmissionCommandUnitTests.cs b/MSStore.CLI.UnitTests/FlightsSubmissionCommandUnitTests.cs index d1800da..007a0a5 100644 --- a/MSStore.CLI.UnitTests/FlightsSubmissionCommandUnitTests.cs +++ b/MSStore.CLI.UnitTests/FlightsSubmissionCommandUnitTests.cs @@ -25,9 +25,9 @@ public async Task FlightSubmissionCommandWithNoParameter() [ "flights", "submission" - ]); + ], 1); - result.Error.Should().Contain("Execute flight submissions related tasks."); + result.Output.Should().Contain("Execute flight submissions related tasks."); } [TestMethod] diff --git a/MSStore.CLI.UnitTests/MSStore.CLI.UnitTests.csproj b/MSStore.CLI.UnitTests/MSStore.CLI.UnitTests.csproj index 9df47de..d3b751c 100644 --- a/MSStore.CLI.UnitTests/MSStore.CLI.UnitTests.csproj +++ b/MSStore.CLI.UnitTests/MSStore.CLI.UnitTests.csproj @@ -1,4 +1,4 @@ - + net9.0;net9.0-windows10.0.17763.0 @@ -44,22 +44,22 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/MSStore.CLI.UnitTests/ProjectConfiguratorFactoryTests.cs b/MSStore.CLI.UnitTests/ProjectConfiguratorFactoryTests.cs index 0ab7a1e..b96c104 100644 --- a/MSStore.CLI.UnitTests/ProjectConfiguratorFactoryTests.cs +++ b/MSStore.CLI.UnitTests/ProjectConfiguratorFactoryTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.CommandLine.Hosting; using Microsoft.Extensions.DependencyInjection; using MSStore.CLI.ProjectConfigurators; @@ -33,11 +32,8 @@ public async Task ProjectConfiguratorFactoryFindsURLProperly(string pathOrUrl, T pathOrUrl = path; } - await RunTestAsync(async (context) => + await RunTestAsync(async (parseResult, host, ct) => { - var ct = context.GetCancellationToken(); - - var host = context.GetHost(); var projectConfiguratorFactory = host.Services.GetService()!; if (testDataProjectSubPath != null && testDataProjectSubPath.Length != 0 && path != null) diff --git a/MSStore.CLI.UnitTests/SettingsCommandUnitTests.cs b/MSStore.CLI.UnitTests/SettingsCommandUnitTests.cs index b7d41ff..0072eeb 100644 --- a/MSStore.CLI.UnitTests/SettingsCommandUnitTests.cs +++ b/MSStore.CLI.UnitTests/SettingsCommandUnitTests.cs @@ -24,8 +24,8 @@ public async Task SettingsCommandWithNoParametersShouldReturnZeroAndShowHelp() "settings" ]); - result.Error.Should().Contain("Usage:"); - result.Error.Should().Contain("settings [command] [options]"); + result.Output.Should().Contain("Usage:"); + result.Output.Should().Contain("settings [command] [options]"); } [TestMethod] diff --git a/MSStore.CLI.UnitTests/SubmissionCommandPackagedUnitTests.cs b/MSStore.CLI.UnitTests/SubmissionCommandPackagedUnitTests.cs index bee5581..e50a05c 100644 --- a/MSStore.CLI.UnitTests/SubmissionCommandPackagedUnitTests.cs +++ b/MSStore.CLI.UnitTests/SubmissionCommandPackagedUnitTests.cs @@ -23,9 +23,9 @@ public async Task SubmissionCommandWithNoParameter() var result = await ParseAndInvokeAsync( [ "submission" - ]); + ], 1); - result.Error.Should().Contain("Executes commands to a store submission."); + result.Output.Should().Contain("Executes commands to a store submission."); } [TestMethod] diff --git a/MSStore.CLI/CommandExtensions.cs b/MSStore.CLI/CommandExtensions.cs deleted file mode 100644 index fa04117..0000000 --- a/MSStore.CLI/CommandExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.CommandLine; -using System.CommandLine.Help; - -namespace MSStore.CLI -{ - internal static class CommandExtensions - { - public static void SetDefaultHelpHandler(this Command command) - { - command.SetHandler((context) => - { - HelpBuilder helpBuilder = new(LocalizationResources.Instance, GetBufferWidth()); - helpBuilder.Write(command, Console.Out); - }); - } - - internal static int GetBufferWidth() - { - try - { - return Console.BufferWidth; - } - catch - { - // Default to 240 - return 240; - } - } - } -} diff --git a/MSStore.CLI/Commands/Apps/GetCommand.cs b/MSStore.CLI/Commands/Apps/GetCommand.cs index ce22ed6..5fc7720 100644 --- a/MSStore.CLI/Commands/Apps/GetCommand.cs +++ b/MSStore.CLI/Commands/Apps/GetCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -22,31 +23,24 @@ internal class GetCommand : Command public GetCommand() : base("get", "Retrieves the Application details.") { - AddArgument(SubmissionCommand.ProductIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + string productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } object? application = null; @@ -54,11 +48,11 @@ public async Task InvokeAsync(InvocationContext context) { try { - if (ProductTypeHelper.Solve(ProductId) == ProductType.Packaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Packaged) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + application = await storePackagedAPI.GetApplicationAsync(productId, ct); } } catch (MSStoreHttpException err) @@ -95,7 +89,7 @@ public async Task InvokeAsync(InvocationContext context) { if (app?.Id == null) { - _ansiConsole.MarkupLine($"Could not find application with ID '{ProductId}'"); + _ansiConsole.MarkupLine($"Could not find application with ID '{productId}'"); return await _telemetryClient.TrackCommandEventAsync(-1, ct); } else diff --git a/MSStore.CLI/Commands/Apps/ListCommand.cs b/MSStore.CLI/Commands/Apps/ListCommand.cs index 94bd6af..d90083f 100644 --- a/MSStore.CLI/Commands/Apps/ListCommand.cs +++ b/MSStore.CLI/Commands/Apps/ListCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Globalization; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,22 +22,15 @@ public ListCommand() { } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public int Invoke(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) - { - var ct = context.GetCancellationToken(); - var appList = await _ansiConsole.Status().StartAsync("Retrieving Managed Applications", async ctx => { try diff --git a/MSStore.CLI/Commands/AppsCommand.cs b/MSStore.CLI/Commands/AppsCommand.cs index f03bdb2..61a17cb 100644 --- a/MSStore.CLI/Commands/AppsCommand.cs +++ b/MSStore.CLI/Commands/AppsCommand.cs @@ -8,12 +8,11 @@ namespace MSStore.CLI.Commands { internal class AppsCommand : Command { - public AppsCommand() + public AppsCommand(ListCommand listCommand, GetCommand getCommand) : base("apps", "Execute apps related tasks.") { - AddCommand(new ListCommand()); - AddCommand(new GetCommand()); - this.SetDefaultHelpHandler(); + Subcommands.Add(listCommand); + Subcommands.Add(getCommand); } } } diff --git a/MSStore.CLI/Commands/Flights/CreateCommand.cs b/MSStore.CLI/Commands/Flights/CreateCommand.cs index c5c0415..e2ac23d 100644 --- a/MSStore.CLI/Commands/Flights/CreateCommand.cs +++ b/MSStore.CLI/Commands/Flights/CreateCommand.cs @@ -7,6 +7,7 @@ using System.CommandLine.Invocation; using System.Linq; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -20,62 +21,65 @@ namespace MSStore.CLI.Commands.Flights { internal class CreateCommand : Command { - public CreateCommand() - : base("create", "Creates a flight for the specified Application and flight.") + private static readonly Argument FriendlyNameArgument; + private static readonly Option> GroupIdsOption; + private static readonly Option RankHigherThanOption; + + static CreateCommand() { - AddArgument(SubmissionCommand.ProductIdArgument); - - AddArgument(new Argument("friendlyName", "The friendly name of the flight.")); - var groupIdsOption = new Option>( - aliases: - [ - "--group-ids", - "-g" - ], - getDefaultValue: Array.Empty, - description: "The group IDs to associate with the flight.") + FriendlyNameArgument = new Argument("friendlyName") + { + Description = "The friendly name of the flight." + }; + + GroupIdsOption = new Option>("--group-ids", "-g") { + DefaultValueFactory = _ => Array.Empty(), + Description = "The group IDs to associate with the flight.", AllowMultipleArgumentsPerToken = true }; - groupIdsOption.AddValidator((result) => + GroupIdsOption.Validators.Add((result) => { var groupIds = result.Tokens.Select(t => t.Value).ToList(); if (groupIds.Count == 0) { - result.ErrorMessage = "At least one group ID must be provided."; + result.AddError("At least one group ID must be provided."); } }); - AddOption(groupIdsOption); + RankHigherThanOption = new Option("--rank-higher-than", "-r") + { + Description = "The flight ID to rank higher than." + }; + } - AddOption(new Option(["--rank-higher-than", "-r"], "The flight ID to rank higher than.")); + public CreateCommand() + : base("create", "Creates a flight for the specified Application and flight.") + { + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(FriendlyNameArgument); + Options.Add(GroupIdsOption); + Options.Add(RankHigherThanOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FriendlyName { get; set; } = null!; - public IEnumerable GroupIds { get; set; } = null!; - public string? RankHigherThan { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var friendlyName = parseResult.GetRequiredValue(FriendlyNameArgument); + var groupIds = parseResult.GetRequiredValue(GroupIdsOption); + var rankHigherThan = parseResult.GetValue(RankHigherThanOption); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var flight = await _ansiConsole.Status().StartAsync("Creating Flight", async ctx => @@ -84,7 +88,7 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var flight = await storePackagedAPI.CreateFlightAsync(ProductId, FriendlyName, GroupIds.ToList(), RankHigherThan, ct); + var flight = await storePackagedAPI.CreateFlightAsync(productId, friendlyName, [.. groupIds], rankHigherThan, ct); ctx.SuccessStatus(_ansiConsole, "[bold green]Created Flight[/]"); diff --git a/MSStore.CLI/Commands/Flights/DeleteCommand.cs b/MSStore.CLI/Commands/Flights/DeleteCommand.cs index 5c54bbd..8a0886c 100644 --- a/MSStore.CLI/Commands/Flights/DeleteCommand.cs +++ b/MSStore.CLI/Commands/Flights/DeleteCommand.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -20,33 +21,26 @@ internal class DeleteCommand : Command public DeleteCommand() : base("delete", "Deletes a flight for the specified Application and flight.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(GetCommand.FlightIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(GetCommand.FlightIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(GetCommand.FlightIdArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } return await _telemetryClient.TrackCommandEventAsync( @@ -56,7 +50,7 @@ await _ansiConsole.Status().StartAsync("Deleting Flight", async ctx => { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - DevCenterError? devCenterError = await storePackagedAPI.DeleteFlightAsync(ProductId, FlightId, ct); + DevCenterError? devCenterError = await storePackagedAPI.DeleteFlightAsync(productId, flightId, ct); ctx.SuccessStatus(_ansiConsole, "[bold green]Deleted Flight[/]"); diff --git a/MSStore.CLI/Commands/Flights/FlightSubmissionCommand.cs b/MSStore.CLI/Commands/Flights/FlightSubmissionCommand.cs index 99fecd7..c46f989 100644 --- a/MSStore.CLI/Commands/Flights/FlightSubmissionCommand.cs +++ b/MSStore.CLI/Commands/Flights/FlightSubmissionCommand.cs @@ -7,17 +7,16 @@ namespace MSStore.CLI.Commands.Flights { internal class FlightSubmissionCommand : Command { - public FlightSubmissionCommand() + public FlightSubmissionCommand(Submission.GetCommand getCommand, Submission.DeleteCommand deleteCommand, Submission.UpdateCommand updateCommand, Submission.PublishCommand publishCommand, Submission.PollCommand pollCommand, Submission.StatusCommand statusCommand, Submission.RolloutCommand rolloutCommand) : base("submission", "Execute flight submissions related tasks.") { - AddCommand(new Submission.GetCommand()); - AddCommand(new Submission.DeleteCommand()); - AddCommand(new Submission.UpdateCommand()); - AddCommand(new Submission.PublishCommand()); - AddCommand(new Submission.PollCommand()); - AddCommand(new Submission.StatusCommand()); - AddCommand(new Submission.RolloutCommand()); - this.SetDefaultHelpHandler(); + Subcommands.Add(getCommand); + Subcommands.Add(deleteCommand); + Subcommands.Add(updateCommand); + Subcommands.Add(publishCommand); + Subcommands.Add(pollCommand); + Subcommands.Add(statusCommand); + Subcommands.Add(rolloutCommand); } } } diff --git a/MSStore.CLI/Commands/Flights/GetCommand.cs b/MSStore.CLI/Commands/Flights/GetCommand.cs index d3361cb..e0d4682 100644 --- a/MSStore.CLI/Commands/Flights/GetCommand.cs +++ b/MSStore.CLI/Commands/Flights/GetCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,39 +22,35 @@ internal class GetCommand : Command static GetCommand() { - FlightIdArgument = new Argument("flightId", "The flight Id."); + FlightIdArgument = new Argument("flightId") + { + Description = "The flight Id." + }; } public GetCommand() : base("get", "Retrieves a flight for the specified Application and flight.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(FlightIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(FlightIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(FlightIdArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var flight = await _ansiConsole.Status().StartAsync("Retrieving Flight", async ctx => @@ -62,7 +59,7 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); ctx.SuccessStatus(_ansiConsole, "[bold green]Retrieved Flight[/]"); diff --git a/MSStore.CLI/Commands/Flights/ListCommand.cs b/MSStore.CLI/Commands/Flights/ListCommand.cs index c052283..c83e625 100644 --- a/MSStore.CLI/Commands/Flights/ListCommand.cs +++ b/MSStore.CLI/Commands/Flights/ListCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Globalization; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -19,79 +20,73 @@ internal class ListCommand : Command public ListCommand() : base("list", "Retrieves all the Flights for the specified Application.") { - AddArgument(SubmissionCommand.ProductIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - return await _telemetryClient.TrackCommandEventAsync( - await _ansiConsole.Status().StartAsync("Retrieving Flights", async ctx => + var flightsList = await _ansiConsole.Status().StartAsync("Retrieving Flights", async ctx => + { + try { - try - { - var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); + var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var flightsList = await storePackagedAPI.GetFlightsAsync(ProductId, ct); + var flightsList = await storePackagedAPI.GetFlightsAsync(productId, ct); - ctx.SuccessStatus(_ansiConsole, "[bold green]Retrieved Flights[/]"); + ctx.SuccessStatus(_ansiConsole, "[bold green]Retrieved Flights[/]"); - if (flightsList?.Count > 0) - { - var table = new Table(); - table.AddColumns(string.Empty, "FlightId", "FriendlyName", "LastPublishedFlightSubmission.Id", "PendingFlightSubmission.Id", "GroupIds", "RankHigherThan"); + return flightsList; + } + catch (Exception err) + { + _logger.LogError(err, "Error while retrieving Flights."); + ctx.ErrorStatus(_ansiConsole, err); + return null; + } + }); - int i = 1; - foreach (var f in flightsList) - { - table.AddRow( - i.ToString(CultureInfo.InvariantCulture), - $"[bold u]{f.FlightId}[/]", - $"[bold u]{f.FriendlyName}[/]", - $"[bold u]{f.LastPublishedFlightSubmission?.Id}[/]", - $"[bold u]{f.PendingFlightSubmission?.Id}[/]", - $"[bold u]{string.Join(", ", f.GroupIds ?? [])}[/]", - $"[bold u]{f.RankHigherThan}[/]"); - i++; - } + if (flightsList?.Count > 0) + { + var table = new Table(); + table.AddColumns(string.Empty, "FlightId", "FriendlyName", "LastPublishedFlightSubmission.Id", "PendingFlightSubmission.Id", "GroupIds", "RankHigherThan"); - AnsiConsole.Write(table); - } - else - { - _ansiConsole.MarkupLine("The application has [bold][u]no[/] Flights[/]."); - } + int i = 1; + foreach (var f in flightsList) + { + table.AddRow( + i.ToString(CultureInfo.InvariantCulture), + $"[bold u]{f.FlightId}[/]", + $"[bold u]{f.FriendlyName}[/]", + $"[bold u]{f.LastPublishedFlightSubmission?.Id}[/]", + $"[bold u]{f.PendingFlightSubmission?.Id}[/]", + $"[bold u]{string.Join(", ", f.GroupIds ?? [])}[/]", + $"[bold u]{f.RankHigherThan}[/]"); + i++; + } - return 0; - } - catch (Exception err) - { - _logger.LogError(err, "Error while retrieving Flights."); - ctx.ErrorStatus(_ansiConsole, err); - return -1; - } - }), ct); + AnsiConsole.Write(table); + return await _telemetryClient.TrackCommandEventAsync(0, ct); + } + else + { + _ansiConsole.MarkupLine("The application has [bold][u]no[/] Flights[/]."); + return await _telemetryClient.TrackCommandEventAsync(-1, ct); + } } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/DeleteCommand.cs b/MSStore.CLI/Commands/Flights/Submission/DeleteCommand.cs index 70799dc..befaac1 100644 --- a/MSStore.CLI/Commands/Flights/Submission/DeleteCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/DeleteCommand.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -20,12 +21,12 @@ internal class DeleteCommand : Command public DeleteCommand() : base("delete", "Deletes the pending package flight submission from the store.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); - AddOption(Commands.Submission.DeleteCommand.NoConfirmOption); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); + Options.Add(Commands.Submission.DeleteCommand.NoConfirmOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IConsoleReader consoleReader, IBrowserLauncher browserLauncher, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IConsoleReader consoleReader, IBrowserLauncher browserLauncher, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); @@ -34,23 +35,16 @@ public DeleteCommand() private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - public bool? NoConfirm { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); + var noConfirm = parseResult.GetValue(Commands.Submission.DeleteCommand.NoConfirmOption); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } IStorePackagedAPI storePackagedAPI = null!; @@ -61,11 +55,11 @@ public async Task InvokeAsync(InvocationContext context) { storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{productId}'/'{flightId}'"); return null; } @@ -103,18 +97,18 @@ public async Task InvokeAsync(InvocationContext context) if (flightSubmissionId == null) { _ansiConsole.WriteLine("Could not find flight submission."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } _ansiConsole.WriteLine($"Found Flight Submission with Id '{flightSubmissionId}'"); - if (NoConfirm == false && !await _consoleReader.YesNoConfirmationAsync("Do you want to delete the pending flight submission?", ct)) + if (noConfirm == false && !await _consoleReader.YesNoConfirmationAsync("Do you want to delete the pending flight submission?", ct)) { return -2; } - var success = await storePackagedAPI.DeleteSubmissionAsync(_ansiConsole, ProductId, FlightId, flightSubmissionId, _browserLauncher, _logger, ct); + var success = await storePackagedAPI.DeleteSubmissionAsync(_ansiConsole, productId, flightId, flightSubmissionId, _browserLauncher, _logger, ct); - return await _telemetryClient.TrackCommandEventAsync(ProductId, success ? 0 : -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, success ? 0 : -1, ct); } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/GetCommand.cs b/MSStore.CLI/Commands/Flights/Submission/GetCommand.cs index 450c618..8f1ebd5 100644 --- a/MSStore.CLI/Commands/Flights/Submission/GetCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/GetCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,33 +22,26 @@ internal class GetCommand : Command public GetCommand() : base("get", "Retrieves the existing package flight submission, either the existing draft or the last published one.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var flightSubmission = await _ansiConsole.Status().StartAsync("Retrieving Flight Submission", async ctx => @@ -56,15 +50,15 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{productId}'/'{flightId}'"); return null; } - return await storePackagedAPI.GetAnyFlightSubmissionAsync(_ansiConsole, ProductId, flight, ctx, _logger, ct); + return await storePackagedAPI.GetAnyFlightSubmissionAsync(_ansiConsole, productId, flight, ctx, _logger, ct); } catch (MSStoreHttpException err) { @@ -91,12 +85,12 @@ public async Task InvokeAsync(InvocationContext context) if (flightSubmission == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmission, SourceGenerationContext.GetCustom(true).DevCenterFlightSubmission)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/PollCommand.cs b/MSStore.CLI/Commands/Flights/Submission/PollCommand.cs index 4536552..240ec25 100644 --- a/MSStore.CLI/Commands/Flights/Submission/PollCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/PollCommand.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -20,11 +21,11 @@ internal class PollCommand : Command public PollCommand() : base("poll", "Polls until the existing flight submission is PUBLISHED or FAILED.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient, IBrowserLauncher browserLauncher) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient, IBrowserLauncher browserLauncher) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); @@ -32,27 +33,20 @@ public PollCommand() private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); private readonly IBrowserLauncher _browserLauncher = browserLauncher ?? throw new ArgumentNullException(nameof(browserLauncher)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); IStorePackagedAPI? storePackagedAPI = null; DevCenterFlight? flight = null; ApplicationSubmissionInfo? flightSubmission = null; - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } DevCenterSubmissionStatusResponse? lastSubmissionStatus = await _ansiConsole.Status().StartAsync("Polling flight submission status", async ctx => @@ -61,11 +55,11 @@ public async Task InvokeAsync(InvocationContext context) { storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{productId}'/'{flightId}'"); return null; } @@ -73,11 +67,11 @@ public async Task InvokeAsync(InvocationContext context) if (flightSubmission?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find flight submission for application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find flight submission for application flight with ID '{productId}'/'{flightId}'"); return null; } - var lastSubmissionStatus = await storePackagedAPI.PollSubmissionStatusAsync(ansiConsole, ProductId, flight.FlightId, flightSubmission.Id, false, _logger, ct: ct); + var lastSubmissionStatus = await storePackagedAPI.PollSubmissionStatusAsync(ansiConsole, productId, flight.FlightId, flightSubmission.Id, false, _logger, ct: ct); ctx.SuccessStatus(_ansiConsole); @@ -95,12 +89,12 @@ public async Task InvokeAsync(InvocationContext context) if (lastSubmissionStatus != null && storePackagedAPI != null && flight?.FlightId != null && flightSubmission?.Id != null) { return await _telemetryClient.TrackCommandEventAsync( - ProductId, - await storePackagedAPI.HandleLastSubmissionStatusAsync(_ansiConsole, lastSubmissionStatus, ProductId, flight.FlightId, flightSubmission.Id, _browserLauncher, _logger, ct), + productId, + await storePackagedAPI.HandleLastSubmissionStatusAsync(_ansiConsole, lastSubmissionStatus, productId, flight.FlightId, flightSubmission.Id, _browserLauncher, _logger, ct), ct); } - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/PublishCommand.cs b/MSStore.CLI/Commands/Flights/Submission/PublishCommand.cs index 81a4787..0d056a6 100644 --- a/MSStore.CLI/Commands/Flights/Submission/PublishCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/PublishCommand.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -19,48 +20,41 @@ internal class PublishCommand : Command public PublishCommand() : base("publish", "Starts the flight submission process for the existing Draft.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } return await _telemetryClient.TrackCommandEventAsync( - ProductId, + productId, await _ansiConsole.Status().StartAsync("Publishing flight submission", async ctx => { try { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{productId}'/'{flightId}'"); return -1; } @@ -68,11 +62,11 @@ await _ansiConsole.Status().StartAsync("Publishing flight submission", async ctx if (flightSubmission?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find flight submission for application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find flight submission for application flight with ID '{productId}'/'{flightId}'"); return -1; } - var flightSubmissionCommit = await storePackagedAPI.CommitFlightSubmissionAsync(ProductId, FlightId, flightSubmission.Id, ct); + var flightSubmissionCommit = await storePackagedAPI.CommitFlightSubmissionAsync(productId, flightId, flightSubmission.Id, ct); if (flightSubmissionCommit == null) { @@ -85,7 +79,7 @@ await _ansiConsole.Status().StartAsync("Publishing flight submission", async ctx return 0; } - ctx.ErrorStatus(_ansiConsole, $"Could not commit flight submission for application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not commit flight submission for application flight with ID '{productId}'/'{flightId}'"); _ansiConsole.MarkupLine($"[red]{flightSubmissionCommit.ToErrorMessage()}[/]"); return -1; diff --git a/MSStore.CLI/Commands/Flights/Submission/Rollout/FinalizeCommand.cs b/MSStore.CLI/Commands/Flights/Submission/Rollout/FinalizeCommand.cs index f91130a..e07e642 100644 --- a/MSStore.CLI/Commands/Flights/Submission/Rollout/FinalizeCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/Rollout/FinalizeCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,35 +22,28 @@ internal class FinalizeCommand : Command public FinalizeCommand() : base("finalize", "Finalizes the flight rollout of a submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); - AddOption(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); + Options.Add(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - public string? SubmissionId { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); + var submissionId = parseResult.GetValue(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var flightSubmissionRollout = await _ansiConsole.Status().StartAsync("Finalizing Flight Submission Rollout", async ctx => @@ -58,26 +52,26 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - if (SubmissionId == null) + if (submissionId == null) { - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{productId}'/'{flightId}'"); return null; } - SubmissionId = flight.GetAnyFlightSubmissionId(); + submissionId = flight.GetAnyFlightSubmissionId(); - if (SubmissionId == null) + if (submissionId == null) { ctx.ErrorStatus(_ansiConsole, "Could not find the flight submission. Please check the ProductId/FlightId."); return null; } } - return await storePackagedAPI.FinalizePackageRolloutAsync(ProductId, SubmissionId, FlightId, ct); + return await storePackagedAPI.FinalizePackageRolloutAsync(productId, submissionId, flightId, ct); } catch (MSStoreHttpException err) { @@ -104,12 +98,12 @@ public async Task InvokeAsync(InvocationContext context) if (flightSubmissionRollout == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/Rollout/GetCommand.cs b/MSStore.CLI/Commands/Flights/Submission/Rollout/GetCommand.cs index aadb7e6..d0e3625 100644 --- a/MSStore.CLI/Commands/Flights/Submission/Rollout/GetCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/Rollout/GetCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,35 +22,28 @@ internal class GetCommand : Command public GetCommand() : base("get", "Retrieves the flight rollout status of a submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); - AddOption(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); + Options.Add(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - public string? SubmissionId { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); + var submissionId = parseResult.GetValue(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var flightSubmissionRollout = await _ansiConsole.Status().StartAsync("Retrieving Flight Submission Rollout", async ctx => @@ -58,26 +52,26 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - if (SubmissionId == null) + if (submissionId == null) { - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{productId}'/'{flightId}'"); return null; } - SubmissionId = flight.GetAnyFlightSubmissionId(); + submissionId = flight.GetAnyFlightSubmissionId(); - if (SubmissionId == null) + if (submissionId == null) { ctx.ErrorStatus(_ansiConsole, "Could not find the flight submission. Please check the ProductId/FlightId."); return null; } } - return await storePackagedAPI.GetPackageRolloutAsync(ProductId, SubmissionId, FlightId, ct); + return await storePackagedAPI.GetPackageRolloutAsync(productId, submissionId, flightId, ct); } catch (MSStoreHttpException err) { @@ -104,12 +98,12 @@ public async Task InvokeAsync(InvocationContext context) if (flightSubmissionRollout == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/Rollout/HaltCommand.cs b/MSStore.CLI/Commands/Flights/Submission/Rollout/HaltCommand.cs index 2ca7656..b7316d4 100644 --- a/MSStore.CLI/Commands/Flights/Submission/Rollout/HaltCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/Rollout/HaltCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,35 +22,28 @@ internal class HaltCommand : Command public HaltCommand() : base("halt", "Halts the flight rollout of a submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); - AddOption(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); + Options.Add(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - public string? SubmissionId { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); + var submissionId = parseResult.GetValue(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var flightSubmissionRollout = await _ansiConsole.Status().StartAsync("Halting Flight Submission Rollout", async ctx => @@ -58,26 +52,26 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - if (SubmissionId == null) + if (submissionId == null) { - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{productId}'/'{flightId}'"); return null; } - SubmissionId = flight.GetAnyFlightSubmissionId(); + submissionId = flight.GetAnyFlightSubmissionId(); - if (SubmissionId == null) + if (submissionId == null) { ctx.ErrorStatus(_ansiConsole, "Could not find the flight submission. Please check the ProductId/FlightId."); return null; } } - return await storePackagedAPI.HaltPackageRolloutAsync(ProductId, SubmissionId, FlightId, ct); + return await storePackagedAPI.HaltPackageRolloutAsync(productId, submissionId, flightId, ct); } catch (MSStoreHttpException err) { @@ -104,12 +98,12 @@ public async Task InvokeAsync(InvocationContext context) if (flightSubmissionRollout == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/Rollout/UpdateCommand.cs b/MSStore.CLI/Commands/Flights/Submission/Rollout/UpdateCommand.cs index bebd214..767bd01 100644 --- a/MSStore.CLI/Commands/Flights/Submission/Rollout/UpdateCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/Rollout/UpdateCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -18,50 +19,49 @@ namespace MSStore.CLI.Commands.Flights.Submission.Rollout { internal class UpdateCommand : Command { + private static readonly Argument PercentageArgument; + + static UpdateCommand() + { + PercentageArgument = new Argument("percentage") + { + Description = "The percentage of users that will receive the submission rollout." + }; + } + public UpdateCommand() : base("update", "Update the flight rollout percentage of a submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); - AddOption(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); - - var percentage = new Argument( - "percentage", - description: "The percentage of users that will receive the submission rollout."); - AddArgument(percentage); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); + Options.Add(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); + Arguments.Add(PercentageArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - public string? SubmissionId { get; set; } - public float Percentage { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); + var submissionId = parseResult.GetValue(Commands.Submission.Rollout.GetCommand.SubmissionIdOption); + var percentage = parseResult.GetRequiredValue(PercentageArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - if (Percentage < 0 || Percentage > 100) + if (percentage < 0 || percentage > 100) { _ansiConsole.WriteLine("The percentage must be between 0 and 100."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var flightSubmissionRollout = await _ansiConsole.Status().StartAsync("Updating Flight Submission Rollout", async ctx => @@ -70,26 +70,26 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - if (SubmissionId == null) + if (submissionId == null) { - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{productId}'/'{flightId}'"); return null; } - SubmissionId = flight.GetAnyFlightSubmissionId(); + submissionId = flight.GetAnyFlightSubmissionId(); - if (SubmissionId == null) + if (submissionId == null) { ctx.ErrorStatus(_ansiConsole, "Could not find the flight submission. Please check the ProductId/FlightId."); return null; } } - return await storePackagedAPI.UpdatePackageRolloutPercentageAsync(ProductId, SubmissionId, FlightId, Percentage, ct); + return await storePackagedAPI.UpdatePackageRolloutPercentageAsync(productId, submissionId, flightId, percentage, ct); } catch (MSStoreHttpException err) { @@ -116,12 +116,12 @@ public async Task InvokeAsync(InvocationContext context) if (flightSubmissionRollout == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(flightSubmissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/RolloutCommand.cs b/MSStore.CLI/Commands/Flights/Submission/RolloutCommand.cs index f170646..7500ee9 100644 --- a/MSStore.CLI/Commands/Flights/Submission/RolloutCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/RolloutCommand.cs @@ -7,14 +7,13 @@ namespace MSStore.CLI.Commands.Flights.Submission { internal class RolloutCommand : Command { - public RolloutCommand() + public RolloutCommand(Rollout.GetCommand getCommand, Rollout.UpdateCommand updateCommand, Rollout.HaltCommand haltCommand, Rollout.FinalizeCommand finalizeCommand) : base("rollout", "Execute flight rollout related operations") { - AddCommand(new Rollout.GetCommand()); - AddCommand(new Rollout.UpdateCommand()); - AddCommand(new Rollout.HaltCommand()); - AddCommand(new Rollout.FinalizeCommand()); - this.SetDefaultHelpHandler(); + Subcommands.Add(getCommand); + Subcommands.Add(updateCommand); + Subcommands.Add(haltCommand); + Subcommands.Add(finalizeCommand); } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/StatusCommand.cs b/MSStore.CLI/Commands/Flights/Submission/StatusCommand.cs index 120baf7..f09cc6d 100644 --- a/MSStore.CLI/Commands/Flights/Submission/StatusCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/StatusCommand.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -19,33 +20,26 @@ internal class StatusCommand : Command public StatusCommand() : base("status", "Retrieves the current status of the store flight submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var devCenterFlightSubmission = await _ansiConsole.Status().StartAsync("Retrieving flight submission status", async ctx => @@ -54,15 +48,15 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application flight with ID '{productId}'/'{flightId}'"); return null; } - return await storePackagedAPI.GetAnyFlightSubmissionAsync(_ansiConsole, ProductId, flight, ctx, _logger, ct); + return await storePackagedAPI.GetAnyFlightSubmissionAsync(_ansiConsole, productId, flight, ctx, _logger, ct); } catch (Exception err) { @@ -74,7 +68,7 @@ public async Task InvokeAsync(InvocationContext context) if (devCenterFlightSubmission?.Id == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } if (devCenterFlightSubmission.Status != null) @@ -82,9 +76,9 @@ public async Task InvokeAsync(InvocationContext context) ansiConsole.MarkupLine($"Submission Status = [green]{devCenterFlightSubmission.Status}[/]"); } - devCenterFlightSubmission.StatusDetails?.PrintAllTables(_ansiConsole, ProductId, devCenterFlightSubmission.Id, _logger); + devCenterFlightSubmission.StatusDetails?.PrintAllTables(_ansiConsole, productId, devCenterFlightSubmission.Id, _logger); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Flights/Submission/UpdateCommand.cs b/MSStore.CLI/Commands/Flights/Submission/UpdateCommand.cs index 5758f71..67e0e90 100644 --- a/MSStore.CLI/Commands/Flights/Submission/UpdateCommand.cs +++ b/MSStore.CLI/Commands/Flights/Submission/UpdateCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -19,47 +20,44 @@ namespace MSStore.CLI.Commands.Flights.Submission { internal class UpdateCommand : Command { + private static readonly Argument ProductArgument; + + static UpdateCommand() + { + ProductArgument = new Argument("product") + { + Description = "The updated JSON product representation." + }; + } + public UpdateCommand() : base("update", "Updates the existing flight draft with the provided JSON.") { - var product = new Argument( - "product", - description: "The updated JSON product representation."); - - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(Flights.GetCommand.FlightIdArgument); - AddArgument(product); - AddOption(SubmissionCommand.SkipInitialPolling); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(Flights.GetCommand.FlightIdArgument); + Arguments.Add(ProductArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string Product { get; set; } = null!; - public bool SkipInitialPolling { get; set; } - public string ProductId { get; set; } = null!; - public string FlightId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var flightId = parseResult.GetRequiredValue(Flights.GetCommand.FlightIdArgument); + var product = parseResult.GetRequiredValue(ProductArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - var updateFlightSubmission = JsonSerializer.Deserialize(Product, SourceGenerationContext.GetCustom().DevCenterFlightSubmissionUpdate); + var updateFlightSubmission = JsonSerializer.Deserialize(product, SourceGenerationContext.GetCustom().DevCenterFlightSubmissionUpdate); if (updateFlightSubmission == null) { @@ -74,11 +72,11 @@ public async Task InvokeAsync(InvocationContext context) { storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var flight = await storePackagedAPI.GetFlightAsync(ProductId, FlightId, ct); + var flight = await storePackagedAPI.GetFlightAsync(productId, flightId, ct); if (flight?.FlightId == null) { - throw new MSStoreException($"Could not find application flight with ID '{ProductId}'/'{FlightId}'"); + throw new MSStoreException($"Could not find application flight with ID '{productId}'/'{flightId}'"); } return flight; @@ -102,7 +100,7 @@ public async Task InvokeAsync(InvocationContext context) { _ansiConsole.MarkupLine("Could not find an existing flight submission. [b green]Creating new flight submission[/]."); - var flightSubmission = await storePackagedAPI.CreateNewFlightSubmissionAsync(_ansiConsole, ProductId, FlightId, _logger, ct); + var flightSubmission = await storePackagedAPI.CreateNewFlightSubmissionAsync(_ansiConsole, productId, flightId, _logger, ct); submissionId = flightSubmission?.Id; if (submissionId == null) @@ -115,7 +113,7 @@ public async Task InvokeAsync(InvocationContext context) { try { - return await storePackagedAPI.UpdateFlightSubmissionAsync(ProductId, FlightId, submissionId, updateFlightSubmission, ct); + return await storePackagedAPI.UpdateFlightSubmissionAsync(productId, flightId, submissionId, updateFlightSubmission, ct); } catch (Exception err) { @@ -127,12 +125,12 @@ public async Task InvokeAsync(InvocationContext context) if (updatedFlightSubmission == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(updatedFlightSubmission, SourceGenerationContext.GetCustom(true).DevCenterFlightSubmission)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/FlightsCommand.cs b/MSStore.CLI/Commands/FlightsCommand.cs index 36b587e..ee6b715 100644 --- a/MSStore.CLI/Commands/FlightsCommand.cs +++ b/MSStore.CLI/Commands/FlightsCommand.cs @@ -8,15 +8,14 @@ namespace MSStore.CLI.Commands { internal class FlightsCommand : Command { - public FlightsCommand() + public FlightsCommand(ListCommand listCommand, GetCommand getCommand, DeleteCommand deleteCommand, CreateCommand createCommand, FlightSubmissionCommand flightSubmissionCommand) : base("flights", "Execute flights related tasks.") { - AddCommand(new ListCommand()); - AddCommand(new GetCommand()); - AddCommand(new DeleteCommand()); - AddCommand(new CreateCommand()); - AddCommand(new FlightSubmissionCommand()); - this.SetDefaultHelpHandler(); + Subcommands.Add(listCommand); + Subcommands.Add(getCommand); + Subcommands.Add(deleteCommand); + Subcommands.Add(createCommand); + Subcommands.Add(flightSubmissionCommand); } } } diff --git a/MSStore.CLI/Commands/InfoCommand.cs b/MSStore.CLI/Commands/InfoCommand.cs index 3eee5c0..d4fe0f7 100644 --- a/MSStore.CLI/Commands/InfoCommand.cs +++ b/MSStore.CLI/Commands/InfoCommand.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -20,21 +21,14 @@ public InfoCommand() { } - public new class Handler(IConfigurationManager configurationManager, TelemetryClient telemetryClient, ILogger logger) : ICommandHandler + public class Handler(IConfigurationManager configurationManager, TelemetryClient telemetryClient, ILogger logger) : AsynchronousCommandLineAction { private readonly IConfigurationManager _configurationManager = configurationManager ?? throw new ArgumentNullException(nameof(configurationManager)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - public int Invoke(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) - { - var ct = context.GetCancellationToken(); - var config = await _configurationManager.LoadAsync(ct: ct); var table = new Table @@ -58,7 +52,7 @@ public async Task InvokeAsync(InvocationContext context) table.AddRow($"[bold u]Certificate Path[/]", $"[bold u]{config.CertificateFilePath}[/]"); } - bool verbose = context.ParseResult.IsVerbose(); + bool verbose = parseResult.IsVerbose(); if (verbose && !string.IsNullOrEmpty(config.StoreApiServiceUrl)) { diff --git a/MSStore.CLI/Commands/InitCommand.cs b/MSStore.CLI/Commands/InitCommand.cs index 405ab44..f0f31f4 100644 --- a/MSStore.CLI/Commands/InitCommand.cs +++ b/MSStore.CLI/Commands/InitCommand.cs @@ -26,15 +26,22 @@ namespace MSStore.CLI.Commands { internal class InitCommand : Command { - internal static readonly Argument PathOrUrl; - internal static readonly Option Output; - internal static readonly Option> Arch; - internal static readonly Option Version; + internal static readonly Argument PathOrUrlArgument; + private static readonly Option PublisherDisplayNameOption; + private static readonly Option PackageOption; + private static readonly Option PublishOption; + internal static readonly Option OutputOption; + internal static readonly Option> ArchOption; + internal static readonly Option VersionOption; static InitCommand() { - PathOrUrl = new Argument("pathOrUrl", () => Directory.GetCurrentDirectory().ToString(), "The root directory path where the project file is, or a public URL that points to a PWA."); - PathOrUrl.AddValidator((result) => + PathOrUrlArgument = new Argument("pathOrUrl") + { + DefaultValueFactory = _ => Directory.GetCurrentDirectory().ToString(), + Description = "The root directory path where the project file is, or a public URL that points to a PWA.", + }; + PathOrUrlArgument.Validators.Add((result) => { var pathOrUrl = result.Tokens.SingleOrDefault()?.Value ?? Directory.GetCurrentDirectory().ToString(); @@ -60,38 +67,41 @@ bool IsUri() FileInfo? filePath = new FileInfo(pathOrUrl); if (!filePath.Exists) { - result.ErrorMessage = $"File or directory does not exist: '{pathOrUrl}'.{Environment.NewLine}"; + result.AddError($"File or directory does not exist: '{pathOrUrl}'.{Environment.NewLine}"); } } } }); - Output = new Option( - aliases: - [ - "--output", - "-o" - ], - description: "The output directory where the packaged app will be stored. If not provided, the default directory for each different type of app will be used."); - - Arch = new Option>( - aliases: - [ - "--arch", - "-a" - ], - description: "The architecture(s) to build for. If not provided, the default architecture for the current OS, and project type, will be used.") + PublisherDisplayNameOption = new Option("--publisherDisplayName", "-n") + { + Description = "The Publisher Display Name used to configure the application. If provided, avoids an extra APIs call." + }; + + PackageOption = new Option("--package") + { + Description = "If supported by the app type, automatically packs the project." + }; + + PublishOption = new Option("--publish") + { + Description = "If supported by the app type, automatically publishes the project. Implies '--package true'" + }; + + OutputOption = new Option("--output", "-o") { - AllowMultipleArgumentsPerToken = true, + Description = "The output directory where the packaged app will be stored. If not provided, the default directory for each different type of app will be used." }; - Version = new Option( - aliases: - [ - "--version", - "-ver" - ], - parseArgument: result => + ArchOption = new Option>("--arch", "-a") + { + Description = "The architecture(s) to build for. If not provided, the default architecture for the current OS, and project type, will be used.", + AllowMultipleArgumentsPerToken = true + }; + + VersionOption = new Option("--version", "-ver") + { + CustomParser = result => { var version = result.Tokens.Single().Value; if (System.Version.TryParse(version, out var parsedVersion)) @@ -99,57 +109,28 @@ bool IsUri() return parsedVersion; } - result.ErrorMessage = $"Invalid version: '{version}'.{Environment.NewLine}"; + result.AddError($"Invalid version: '{version}'.{Environment.NewLine}"); return null; }, - description: "The version used when building the app. If not provided, the version from the project file will be used."); + Description = "The version used when building the app. If not provided, the version from the project file will be used." + }; } public InitCommand() : base("init", "Helps you setup your application to publish to the Microsoft Store.") { - AddArgument(PathOrUrl); - - var publisherDisplayName = new Option( - aliases: - [ - "--publisherDisplayName", - "-n" - ], - description: "The Publisher Display Name used to configure the application. If provided, avoids an extra APIs call."); - - AddOption(publisherDisplayName); - - var package = new Option( - aliases: - [ - "--package" - ], - description: "If supported by the app type, automatically packs the project."); - - AddOption(package); - - var publish = new Option( - aliases: - [ - "--publish" - ], - description: "If supported by the app type, automatically publishes the project. Implies '--package true'"); - - AddOption(publish); - - AddOption(PublishCommand.FlightIdOption); - - AddOption(Output); - - AddOption(Arch); - - AddOption(Version); - - AddOption(PublishCommand.PackageRolloutPercentageOption); + Arguments.Add(PathOrUrlArgument); + Options.Add(PublisherDisplayNameOption); + Options.Add(PackageOption); + Options.Add(PublishOption); + Options.Add(PublishCommand.FlightIdOption); + Options.Add(OutputOption); + Options.Add(ArchOption); + Options.Add(VersionOption); + Options.Add(PublishCommand.PackageRolloutPercentageOption); } - public new class Handler( + public class Handler( ILogger logger, IBrowserLauncher browserLauncher, IConsoleReader consoleReader, @@ -160,7 +141,7 @@ public InitCommand() IImageConverter imageConverter, IConfigurationManager configurationManager, IAnsiConsole ansiConsole, - TelemetryClient telemetryClient) : ICommandHandler + TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IBrowserLauncher _browserLauncher = browserLauncher ?? throw new ArgumentNullException(nameof(browserLauncher)); @@ -174,65 +155,50 @@ public InitCommand() private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string PathOrUrl { get; set; } = null!; - - public string? PublisherDisplayName { get; set; } = null!; - - public bool? Package { get; set; } - - public bool? Publish { get; set; } - - public string? FlightId { get; set; } - - public Version? Version { get; set; } = null!; - - public float? PackageRolloutPercentage { get; set; } - - public DirectoryInfo? Output { get; set; } = null!; - - public IEnumerable? Arch { get; set; } = null!; - - public int Invoke(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) - { - var ct = context.GetCancellationToken(); - - var configurator = await _projectConfiguratorFactory.FindProjectConfiguratorAsync(PathOrUrl, ct); + var pathOrUrl = parseResult.GetRequiredValue(PathOrUrlArgument); + var publisherDisplayName = parseResult.GetValue(PublisherDisplayNameOption); + var package = parseResult.GetValue(PackageOption); + var publish = parseResult.GetValue(PublishOption); + var flightId = parseResult.GetValue(PublishCommand.FlightIdOption); + var version = parseResult.GetValue(VersionOption); + var packageRolloutPercentage = parseResult.GetValue(PublishCommand.PackageRolloutPercentageOption); + var output = parseResult.GetValue(OutputOption); + var arch = parseResult.GetValue(ArchOption); + + var configurator = await _projectConfiguratorFactory.FindProjectConfiguratorAsync(pathOrUrl, ct); var props = new Dictionary { { - "withPDN", (PublisherDisplayName != null).ToString() + "withPDN", (publisherDisplayName != null).ToString() }, { - "Package", (Package == true).ToString() + "Package", (package == true).ToString() }, { - "Publish", (Publish == true).ToString() + "Publish", (publish == true).ToString() } }; if (configurator == null) { - _ansiConsole.WriteLine(string.Format(CultureInfo.InvariantCulture, "We could not find a project configurator for the project at '{0}'.", PathOrUrl)); + _ansiConsole.WriteLine(string.Format(CultureInfo.InvariantCulture, "We could not find a project configurator for the project at '{0}'.", pathOrUrl)); props["ProjType"] = "NF"; return await _telemetryClient.TrackCommandEventAsync(-1, props, ct); } props["ProjType"] = configurator.ToString() ?? string.Empty; - var validationResult = configurator.ValidateCommand(PathOrUrl, Output, Package, Publish); + var validationResult = configurator.ValidateCommand(pathOrUrl, output, package, publish); if (validationResult.HasValue) { return await _telemetryClient.TrackCommandEventAsync(validationResult.Value, props, ct); } - if (string.IsNullOrEmpty(PublisherDisplayName)) + if (string.IsNullOrEmpty(publisherDisplayName)) { if (_partnerCenterManager.Enabled) { @@ -278,25 +244,25 @@ public async Task InvokeAsync(InvocationContext context) return await _telemetryClient.TrackCommandEventAsync(-3, props, ct); } - PublisherDisplayName = account.Name; + publisherDisplayName = account.Name; } else { var config = await _configurationManager.LoadAsync(ct: ct); - PublisherDisplayName = config.PublisherDisplayName; + publisherDisplayName = config.PublisherDisplayName; - if (string.IsNullOrEmpty(PublisherDisplayName)) + if (string.IsNullOrEmpty(publisherDisplayName)) { - PublisherDisplayName = await _consoleReader.RequestStringAsync("Please, provide the PublisherDisplayName", false, ct); - if (string.IsNullOrEmpty(PublisherDisplayName)) + publisherDisplayName = await _consoleReader.RequestStringAsync("Please, provide the PublisherDisplayName", false, ct); + if (string.IsNullOrEmpty(publisherDisplayName)) { _ansiConsole.MarkupLine("[bold red]Invalid Publisher Display Name[/]"); return await _telemetryClient.TrackCommandEventAsync(-1, props, ct); } - if (config.PublisherDisplayName != PublisherDisplayName) + if (config.PublisherDisplayName != publisherDisplayName) { - config.PublisherDisplayName = PublisherDisplayName; + config.PublisherDisplayName = publisherDisplayName; await _configurationManager.SaveAsync(config, ct); } } @@ -313,16 +279,16 @@ public async Task InvokeAsync(InvocationContext context) _ansiConsole.WriteLine($"This seems to be a {configurator} project."); - bool verbose = context.ParseResult.IsVerbose(); + bool verbose = parseResult.IsVerbose(); if (verbose) { - _ansiConsole.WriteLine($"Using PublisherDisplayName: {PublisherDisplayName}"); + _ansiConsole.WriteLine($"Using PublisherDisplayName: {publisherDisplayName}"); } _ansiConsole.WriteLine("Let's set it up for you!"); _ansiConsole.WriteLine(); - var (result, outputDirectory) = await configurator.ConfigureAsync(PathOrUrl, Output, PublisherDisplayName, app, Version, storePackagedAPI, ct); + var (result, outputDirectory) = await configurator.ConfigureAsync(pathOrUrl, output, publisherDisplayName, app, version, storePackagedAPI, ct); if (result != 0) { @@ -331,13 +297,13 @@ public async Task InvokeAsync(InvocationContext context) if (outputDirectory != null) { - Output = outputDirectory; + output = outputDirectory; } - await configurator.ValidateImagesAsync(_ansiConsole, PathOrUrl, _imageConverter, _logger, ct); + await configurator.ValidateImagesAsync(_ansiConsole, pathOrUrl, _imageConverter, _logger, ct); outputDirectory = null; - if (Package == true || Publish == true) + if (package == true || publish == true) { var projectPackager = configurator as IProjectPackager; if (projectPackager == null) @@ -346,7 +312,7 @@ public async Task InvokeAsync(InvocationContext context) return await _telemetryClient.TrackCommandEventAsync(-4, props, ct); } - var buildArchs = Arch?.Distinct(); + var buildArchs = arch?.Distinct(); if (buildArchs?.Any() != true) { buildArchs = projectPackager.DefaultBuildArchs; @@ -363,7 +329,7 @@ public async Task InvokeAsync(InvocationContext context) return await _telemetryClient.TrackCommandEventAsync(-6, props, ct); } - (result, outputDirectory) = await projectPackager.PackageAsync(PathOrUrl, app, buildArchs, Version, Output, storePackagedAPI, ct); + (result, outputDirectory) = await projectPackager.PackageAsync(pathOrUrl, app, buildArchs, version, output, storePackagedAPI, ct); } if (result != 0) @@ -371,7 +337,7 @@ public async Task InvokeAsync(InvocationContext context) return await _telemetryClient.TrackCommandEventAsync(result, props, ct); } - if (Publish == true) + if (publish == true) { var projectPublisher = configurator as IProjectPublisher; if (projectPublisher == null) @@ -380,7 +346,7 @@ public async Task InvokeAsync(InvocationContext context) return await _telemetryClient.TrackCommandEventAsync(-5, props, ct); } - result = await projectPublisher.PublishAsync(PathOrUrl, app, FlightId, outputDirectory, false, PackageRolloutPercentage, storePackagedAPI, ct); + result = await projectPublisher.PublishAsync(pathOrUrl, app, flightId, outputDirectory, false, packageRolloutPercentage, storePackagedAPI, ct); } return await _telemetryClient.TrackCommandEventAsync(result, props, ct); diff --git a/MSStore.CLI/Commands/PackageCommand.cs b/MSStore.CLI/Commands/PackageCommand.cs index 5da3546..09a4403 100644 --- a/MSStore.CLI/Commands/PackageCommand.cs +++ b/MSStore.CLI/Commands/PackageCommand.cs @@ -6,9 +6,9 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Globalization; -using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -24,19 +24,19 @@ internal class PackageCommand : Command public PackageCommand() : base("package", "Helps you package your Microsoft Store Application as an MSIX.") { - AddArgument(InitCommand.PathOrUrl); - AddOption(InitCommand.Version); - AddOption(InitCommand.Output); - AddOption(InitCommand.Arch); + Arguments.Add(InitCommand.PathOrUrlArgument); + Options.Add(InitCommand.VersionOption); + Options.Add(InitCommand.OutputOption); + Options.Add(InitCommand.ArchOption); } - public new class Handler( + public class Handler( IProjectConfiguratorFactory projectConfiguratorFactory, IStoreAPIFactory storeAPIFactory, IImageConverter imageConverter, ILogger logger, IAnsiConsole ansiConsole, - TelemetryClient telemetryClient) : ICommandHandler + TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly IProjectConfiguratorFactory _projectConfiguratorFactory = projectConfiguratorFactory ?? throw new ArgumentNullException(nameof(projectConfiguratorFactory)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); @@ -45,30 +45,20 @@ public PackageCommand() private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string PathOrUrl { get; set; } = null!; - - public Version? Version { get; set; } = null!; - - public DirectoryInfo? Output { get; set; } = null!; - - public IEnumerable? Arch { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var pathOrUrl = parseResult.GetRequiredValue(InitCommand.PathOrUrlArgument); + var version = parseResult.GetValue(InitCommand.VersionOption); + var output = parseResult.GetValue(InitCommand.OutputOption); + var arch = parseResult.GetValue(InitCommand.ArchOption); - var configurator = await _projectConfiguratorFactory.FindProjectConfiguratorAsync(PathOrUrl, ct); + var configurator = await _projectConfiguratorFactory.FindProjectConfiguratorAsync(pathOrUrl, ct); var props = new Dictionary(); if (configurator == null) { - _ansiConsole.WriteLine(string.Format(CultureInfo.InvariantCulture, "We could not find a project configurator for the project at '{0}'.", PathOrUrl)); + _ansiConsole.WriteLine(string.Format(CultureInfo.InvariantCulture, "We could not find a project configurator for the project at '{0}'.", pathOrUrl)); props["ProjType"] = "NF"; return await _telemetryClient.TrackCommandEventAsync(-1, props, ct); } @@ -86,7 +76,7 @@ public async Task InvokeAsync(InvocationContext context) return await _telemetryClient.TrackCommandEventAsync(-4, props, ct); } - var buildArchs = Arch?.Distinct(); + var buildArchs = arch?.Distinct(); if (buildArchs?.Any() != true) { buildArchs = projectPackager.DefaultBuildArchs; @@ -97,7 +87,7 @@ public async Task InvokeAsync(InvocationContext context) props["Archs"] = string.Join(",", buildArchs); } - await configurator.ValidateImagesAsync(_ansiConsole, PathOrUrl, _imageConverter, _logger, ct); + await configurator.ValidateImagesAsync(_ansiConsole, pathOrUrl, _imageConverter, _logger, ct); if (projectPackager.PackageOnlyOnWindows && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -105,7 +95,7 @@ public async Task InvokeAsync(InvocationContext context) return await _telemetryClient.TrackCommandEventAsync(-6, props, ct); } - var (returnCode, outputDirectory) = await projectPackager.PackageAsync(PathOrUrl, null, buildArchs, Version, Output, storePackagedAPI, ct); + var (returnCode, outputDirectory) = await projectPackager.PackageAsync(pathOrUrl, null, buildArchs, version, output, storePackagedAPI, ct); if (returnCode == 0 && outputDirectory != null) { diff --git a/MSStore.CLI/Commands/PublishCommand.cs b/MSStore.CLI/Commands/PublishCommand.cs index 7270302..88c7dec 100644 --- a/MSStore.CLI/Commands/PublishCommand.cs +++ b/MSStore.CLI/Commands/PublishCommand.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -22,17 +23,20 @@ internal class PublishCommand : Command { internal static readonly Option FlightIdOption; internal static readonly Option PackageRolloutPercentageOption; + private static readonly Option InputDirectoryOption; + private static readonly Option AppIdOption; + private static readonly Option NoCommitOption; static PublishCommand() { - FlightIdOption = new Option( - aliases: ["--flightId", "-f"], - description: "Specifies the Flight Id where the package will be published."); - PackageRolloutPercentageOption = new Option( - aliases: ["--packageRolloutPercentage", "-prp"], - description: "Specifies the rollout percentage of the package. The value must be between 0 and 100.", - isDefault: false, - parseArgument: result => + FlightIdOption = new Option("--flightId", "-f") + { + Description = "Specifies the Flight Id where the package will be published." + }; + PackageRolloutPercentageOption = new Option("--packageRolloutPercentage", "-prp") + { + Description = "Specifies the rollout percentage of the package. The value must be between 0 and 100.", + CustomParser = result => { if (result.Tokens.Count == 0) { @@ -42,34 +46,25 @@ static PublishCommand() string? percentage = result.Tokens.Single().Value; if (!float.TryParse(percentage, out float parsedPercentage)) { - result.ErrorMessage = "Invalid rollout percentage. The value must be between 0 and 100."; + result.AddError("Invalid rollout percentage. The value must be between 0 and 100."); return 100f; } else if (parsedPercentage < 0 || parsedPercentage > 100) { - result.ErrorMessage = "Invalid rollout percentage. The value must be between 0 and 100."; + result.AddError("Invalid rollout percentage. The value must be between 0 and 100."); return 100f; } else { return parsedPercentage; } - }); - } + } + }; - public PublishCommand() - : base("publish", "Publishes your Application to the Microsoft Store.") - { - AddArgument(InitCommand.PathOrUrl); - - var inputDirectory = new Option( - aliases: - [ - "--inputDirectory", - "-i" - ], - description: "The directory where the '.msix' or '.msixupload' file to be used for the publishing command. If not provided, the cli will try to find the best candidate based on the 'pathOrUrl' argument.", - parseArgument: result => + InputDirectoryOption = new Option("--inputDirectory", "-i") + { + Description = "The directory where the '.msix' or '.msixupload' file to be used for the publishing command. If not provided, the cli will try to find the best candidate based on the 'pathOrUrl' argument.", + CustomParser = result => { if (result.Tokens.Count == 0) { @@ -79,49 +74,45 @@ public PublishCommand() string? directoryPath = result.Tokens.Single().Value; if (!Directory.Exists(directoryPath)) { - result.ErrorMessage = "Input directory does not exist."; + result.AddError("Input directory does not exist."); return null; } else { return new DirectoryInfo(directoryPath); } - }); - - AddOption(inputDirectory); - - var appIdOption = new Option( - aliases: - [ - "--appId", - "-id" - ], - description: "Specifies the Application Id. Only needed if the project has not been initialized before with the 'init' command."); - - AddOption(appIdOption); - - var noCommitOption = new Option( - aliases: - [ - "--noCommit", - "-nc" - ], - description: "Disables committing the submission, keeping it in draft state.", - getDefaultValue: () => false); + } + }; - AddOption(noCommitOption); + AppIdOption = new Option("--appId", "-id") + { + Description = "Specifies the Application Id. Only needed if the project has not been initialized before with the 'init' command." + }; - AddOption(FlightIdOption); + NoCommitOption = new Option("--noCommit", "-nc") + { + Description = "Disables committing the submission, keeping it in draft state.", + DefaultValueFactory = _ => false + }; + } - AddOption(PackageRolloutPercentageOption); + public PublishCommand() + : base("publish", "Publishes your Application to the Microsoft Store.") + { + Arguments.Add(InitCommand.PathOrUrlArgument); + Options.Add(InputDirectoryOption); + Options.Add(AppIdOption); + Options.Add(NoCommitOption); + Options.Add(FlightIdOption); + Options.Add(PackageRolloutPercentageOption); } - public new class Handler( + public class Handler( IProjectConfiguratorFactory projectConfiguratorFactory, IStoreAPIFactory storeAPIFactory, TelemetryClient telemetryClient, IAnsiConsole ansiConsole, - ILogger logger) : ICommandHandler + ILogger logger) : AsynchronousCommandLineAction { private readonly IProjectConfiguratorFactory _projectConfiguratorFactory = projectConfiguratorFactory ?? throw new ArgumentNullException(nameof(projectConfiguratorFactory)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); @@ -129,33 +120,22 @@ public PublishCommand() private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - public string PathOrUrl { get; set; } = null!; - - public string? AppId { get; set; } - - public string? FlightId { get; set; } - public float? PackageRolloutPercentage { get; set; } - - public DirectoryInfo? InputDirectory { get; set; } = null!; - - public bool NoCommit { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var pathOrUrl = parseResult.GetRequiredValue(InitCommand.PathOrUrlArgument); + var appId = parseResult.GetValue(AppIdOption); + var flightId = parseResult.GetValue(FlightIdOption); + var packageRolloutPercentage = parseResult.GetValue(PackageRolloutPercentageOption); + var inputDirectory = parseResult.GetValue(InputDirectoryOption); + var noCommit = parseResult.GetRequiredValue(NoCommitOption); - var projectPublisher = await _projectConfiguratorFactory.FindProjectPublisherAsync(PathOrUrl, ct); + var projectPublisher = await _projectConfiguratorFactory.FindProjectPublisherAsync(pathOrUrl, ct); var props = new Dictionary(); if (projectPublisher == null) { - _ansiConsole.WriteLine(string.Format(CultureInfo.InvariantCulture, "We could not find a project publisher for the project at '{0}'.", PathOrUrl)); + _ansiConsole.WriteLine(string.Format(CultureInfo.InvariantCulture, "We could not find a project publisher for the project at '{0}'.", pathOrUrl)); props["ProjType"] = "NF"; return await _telemetryClient.TrackCommandEventAsync(-1, props, ct); } @@ -168,13 +148,13 @@ public async Task InvokeAsync(InvocationContext context) API.Packaged.Models.DevCenterApplication? app = null; - if (!string.IsNullOrEmpty(AppId)) + if (!string.IsNullOrEmpty(appId)) { app = await _ansiConsole.Status().StartAsync("Retrieving application...", async ctx => { try { - var app = await storePackagedAPI.GetApplicationAsync(AppId, ct); + var app = await storePackagedAPI.GetApplicationAsync(appId, ct); ctx.SuccessStatus(_ansiConsole, "Ok! Found the app!"); return app; @@ -182,7 +162,7 @@ public async Task InvokeAsync(InvocationContext context) catch (Exception) { ctx.ErrorStatus(_ansiConsole, "Could not retrieve your application. Please make sure you have the correct AppId."); - _logger.LogError("Could not find application with id '{AppId}'.", AppId); + _logger.LogError("Could not find application with id '{AppId}'.", appId); return null; } }); @@ -194,7 +174,7 @@ public async Task InvokeAsync(InvocationContext context) } return await _telemetryClient.TrackCommandEventAsync( - await projectPublisher.PublishAsync(PathOrUrl, app, FlightId, InputDirectory, NoCommit, PackageRolloutPercentage, storePackagedAPI, ct), props, ct); + await projectPublisher.PublishAsync(pathOrUrl, app, flightId, inputDirectory, noCommit, packageRolloutPercentage, storePackagedAPI, ct), props, ct); } } } diff --git a/MSStore.CLI/Commands/ReconfigureCommand.cs b/MSStore.CLI/Commands/ReconfigureCommand.cs index 1046d91..a16ef76 100644 --- a/MSStore.CLI/Commands/ReconfigureCommand.cs +++ b/MSStore.CLI/Commands/ReconfigureCommand.cs @@ -6,6 +6,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using MSStore.CLI.Helpers; @@ -16,106 +17,119 @@ namespace MSStore.CLI.Commands { internal class ReconfigureCommand : Command { - public ReconfigureCommand() - : base("reconfigure", "Re-configure the Microsoft Store Developer CLI.") + private static readonly Option TenantIdOption; + private static readonly Option SellerIdOption; + private static readonly Option ClientIdOption; + private static readonly Option ClientSecretOption; + private static readonly Option CertificateThumbprintOption; + private static readonly Option CertificateFilePathOption; + private static readonly Option CertificatePasswordOption; + private static readonly Option ResetOption; + + static ReconfigureCommand() { - var tenantId = new Option( - aliases: ["--tenantId", "-t"], - description: "Specify the tenant Id that should be used."); + TenantIdOption = new Option("--tenantId", "-t") + { + Description = "Specify the tenant Id that should be used." + }; - var sellerId = new Option( - aliases: ["--sellerId", "-s"], - description: "Specify the seller Id that should be used."); + SellerIdOption = new Option("--sellerId", "-s") + { + Description = "Specify the seller Id that should be used." + }; - var clientId = new Option( - aliases: ["--clientId", "-c"], - description: "Specify the client Id that should be used."); + ClientIdOption = new Option("--clientId", "-c") + { + Description = "Specify the client Id that should be used." + }; - var clientSecret = new Option( - aliases: ["--clientSecret", "-cs"], - description: "Specify the client Secret that should be used."); + ClientSecretOption = new Option("--clientSecret", "-cs") + { + Description = "Specify the client Secret that should be used." + }; - var certificateThumbprint = new Option( - aliases: ["--certificateThumbprint", "-ct"], - description: "Specify the certificate Thumbprint that should be used."); + CertificateThumbprintOption = new Option("--certificateThumbprint", "-ct") + { + Description = "Specify the certificate Thumbprint that should be used." + }; - var certificateFilePath = new Option( - aliases: ["--certificateFilePath", "-cfp"], - description: "Specify the certificate file path that should be used."); + CertificateFilePathOption = new Option("--certificateFilePath", "-cfp") + { + Description = "Specify the certificate file path that should be used." + }; - var certificatePassword = new Option( - aliases: ["--certificatePassword", "-cp"], - description: "Specify the certificate password that should be used."); + CertificatePasswordOption = new Option("--certificatePassword", "-cp") + { + Description = "Specify the certificate password that should be used." + }; - var reset = new Option( - aliases: ["--reset"], - description: "Only reset the credentials, without starting over."); + ResetOption = new Option("--reset") + { + Description = "Only reset the credentials, without starting over." + }; + } - AddOption(tenantId); - AddOption(sellerId); - AddOption(clientId); - AddOption(clientSecret); - AddOption(certificateThumbprint); - AddOption(certificateFilePath); - AddOption(certificatePassword); - AddOption(reset); + public ReconfigureCommand() + : base("reconfigure", "Re-configure the Microsoft Store Developer CLI.") + { + Options.Add(TenantIdOption); + Options.Add(SellerIdOption); + Options.Add(ClientIdOption); + Options.Add(ClientSecretOption); + Options.Add(CertificateThumbprintOption); + Options.Add(CertificateFilePathOption); + Options.Add(CertificatePasswordOption); + Options.Add(ResetOption); } - public new class Handler(ICLIConfigurator cliConfigurator, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ICLIConfigurator cliConfigurator, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ICLIConfigurator _cliConfigurator = cliConfigurator; private readonly IAnsiConsole _ansiConsole = ansiConsole; private readonly TelemetryClient _telemetryClient = telemetryClient; - public Guid? TenantId { get; set; } - public string? SellerId { get; set; } - public Guid? ClientId { get; set; } - public string? ClientSecret { get; set; } - public string? CertificateThumbprint { get; set; } - public FileInfo? CertificateFilePath { get; set; } - public string? CertificatePassword { get; set; } - public bool? Reset { get; set; } - - public int Invoke(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) - { - var ct = context.GetCancellationToken(); - - bool askConfirmation = TenantId == null || - SellerId == null || - ClientId == null || - (ClientSecret == null && - CertificateThumbprint == null && - CertificateFilePath == null); + var tenantId = parseResult.GetValue(TenantIdOption); + var sellerId = parseResult.GetValue(SellerIdOption); + var clientId = parseResult.GetValue(ClientIdOption); + var clientSecret = parseResult.GetValue(ClientSecretOption); + var certificateThumbprint = parseResult.GetValue(CertificateThumbprintOption); + var certificateFilePath = parseResult.GetValue(CertificateFilePathOption); + var certificatePassword = parseResult.GetValue(CertificatePasswordOption); + var reset = parseResult.GetValue(ResetOption); + + bool askConfirmation = tenantId == null || + sellerId == null || + clientId == null || + (clientSecret == null && + certificateThumbprint == null && + certificateFilePath == null); return await _telemetryClient.TrackCommandEventAsync( - (Reset == true + (reset == true ? await _cliConfigurator.ResetAsync(ct: ct) : await _cliConfigurator.ConfigureAsync( _ansiConsole, askConfirmation, - tenantId: TenantId, - sellerId: SellerId, - clientId: ClientId, - clientSecret: ClientSecret, - certificateThumbprint: CertificateThumbprint, - certificateFilePath: CertificateFilePath?.FullName, - certificatePassword: CertificatePassword, + tenantId: tenantId, + sellerId: sellerId, + clientId: clientId, + clientSecret: clientSecret, + certificateThumbprint: certificateThumbprint, + certificateFilePath: certificateFilePath?.FullName, + certificatePassword: certificatePassword, ct: ct)) ? 0 : -1, new Dictionary { - { "reset", (Reset == true).ToString() }, - { "withTenant", (TenantId != null).ToString() }, - { "withSellerId", (SellerId != null).ToString() }, - { "withClientId", (ClientId != null).ToString() }, - { "withClientSecret", (ClientSecret != null).ToString() }, - { "withCertificateThumbprint", (CertificateThumbprint != null).ToString() }, - { "withCertificateFilePath", (CertificateFilePath != null).ToString() }, - { "withCertificatePassword", (CertificatePassword != null).ToString() } + { "reset", (reset == true).ToString() }, + { "withTenant", (tenantId != null).ToString() }, + { "withSellerId", (sellerId != null).ToString() }, + { "withClientId", (clientId != null).ToString() }, + { "withClientSecret", (clientSecret != null).ToString() }, + { "withCertificateThumbprint", (certificateThumbprint != null).ToString() }, + { "withCertificateFilePath", (certificateFilePath != null).ToString() }, + { "withCertificatePassword", (certificatePassword != null).ToString() } }, ct); } diff --git a/MSStore.CLI/Commands/Settings/SetPublisherDisplayNameCommand.cs b/MSStore.CLI/Commands/Settings/SetPublisherDisplayNameCommand.cs index ec46103..31cb9ad 100644 --- a/MSStore.CLI/Commands/Settings/SetPublisherDisplayNameCommand.cs +++ b/MSStore.CLI/Commands/Settings/SetPublisherDisplayNameCommand.cs @@ -4,8 +4,10 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; +using Microsoft.Extensions.Logging; using MSStore.CLI.Helpers; using MSStore.CLI.Services; @@ -14,37 +16,43 @@ namespace MSStore.CLI.Commands.Settings // To be removed when PartnerCenterManager.Enabled == true internal class SetPublisherDisplayNameCommand : Command { + private static readonly Argument PublisherDisplayNameArgument; + + static SetPublisherDisplayNameCommand() + { + PublisherDisplayNameArgument = new Argument("publisherDisplayName") + { + Description = "The Publisher Display Name property that will be set globally." + }; + } + public SetPublisherDisplayNameCommand() : base("setpdn", "Set the Publisher Display Name property that is used by the init command.") { - var publisherDisplayName = new Argument("publisherDisplayName", "The Publisher Display Name property that will be set globally."); - AddArgument(publisherDisplayName); + Arguments.Add(PublisherDisplayNameArgument); } - public new class Handler( + public class Handler( + ILogger logger, IConfigurationManager configurationManager, - TelemetryClient telemetryClient) : ICommandHandler + TelemetryClient telemetryClient) : AsynchronousCommandLineAction { + private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IConfigurationManager _configurationManager = configurationManager ?? throw new ArgumentNullException(nameof(configurationManager)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string? PublisherDisplayName { get; set; } - - public int Invoke(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) - { - var ct = context.GetCancellationToken(); + var publisherDisplayName = parseResult.GetRequiredValue(PublisherDisplayNameArgument); try { var config = await _configurationManager.LoadAsync(ct: ct); - config.PublisherDisplayName = PublisherDisplayName; + config.PublisherDisplayName = publisherDisplayName; await _configurationManager.SaveAsync(config, ct); + _logger.LogInformation("PublisherDisplayName set to '{PublisherDisplayName}'", publisherDisplayName); + return await _telemetryClient.TrackCommandEventAsync(0, ct); } catch diff --git a/MSStore.CLI/Commands/SettingsCommand.cs b/MSStore.CLI/Commands/SettingsCommand.cs index e6091ff..7d22054 100644 --- a/MSStore.CLI/Commands/SettingsCommand.cs +++ b/MSStore.CLI/Commands/SettingsCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Help; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -12,54 +13,47 @@ using MSStore.CLI.Helpers; using MSStore.CLI.Services; using MSStore.CLI.Services.Telemetry; -using Spectre.Console; namespace MSStore.CLI.Commands { internal class SettingsCommand : Command { - public SettingsCommand() - : base("settings", "Change settings of the Microsoft Store Developer CLI.") + private static readonly Option EnableTelemetryOption; + + static SettingsCommand() { - var enableTelemetry = new Option( - "--enableTelemetry", - "Enable (empty/true) or Disable (false) telemetry."); - enableTelemetry.AddAlias("-t"); - AddOption(enableTelemetry); + EnableTelemetryOption = new Option("--enableTelemetry", "-t") + { + Description = "Enable (empty/true) or Disable (false) telemetry." + }; + } - AddCommand(new SetPublisherDisplayNameCommand()); + public SettingsCommand(SetPublisherDisplayNameCommand setPublisherDisplayNameCommand) + : base("settings", "Change settings of the Microsoft Store Developer CLI.") + { + Options.Add(EnableTelemetryOption); - this.SetHandler(() => - { - }); + Subcommands.Add(setPublisherDisplayNameCommand); } - public new class Handler(TelemetryClient telemetryClient, IConfigurationManager telemetryConfigurationManager, IConfigurationManager configurationManager, ILogger logger) : ICommandHandler + public class Handler(TelemetryClient telemetryClient, IConfigurationManager telemetryConfigurationManager, IConfigurationManager configurationManager, ILogger logger) : AsynchronousCommandLineAction { private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); private readonly IConfigurationManager _telemetryConfigurationManager = telemetryConfigurationManager ?? throw new ArgumentNullException(nameof(telemetryConfigurationManager)); private readonly IConfigurationManager _configurationManager = configurationManager ?? throw new ArgumentNullException(nameof(configurationManager)); private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - public bool? EnableTelemetry { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var enableTelemetry = parseResult.GetValue(EnableTelemetryOption); try { var telemetryConfigurations = await _telemetryConfigurationManager.LoadAsync(true, ct); - if (!EnableTelemetry.HasValue) + if (!enableTelemetry.HasValue) { - HelpBuilder helpBuilder = new(LocalizationResources.Instance, CommandExtensions.GetBufferWidth()); - helpBuilder.Write(context.ParseResult.CommandResult.Command, Console.Out); + new HelpAction().Invoke(parseResult); _logger.LogInformation("TelemetryEnabled = {TelemetryEnabled}", telemetryConfigurations.TelemetryEnabled); @@ -68,7 +62,8 @@ public async Task InvokeAsync(InvocationContext context) } else { - telemetryConfigurations.TelemetryEnabled = EnableTelemetry.Value; + telemetryConfigurations.TelemetryEnabled = enableTelemetry.Value; + _logger.LogInformation("TelemetryEnabled set to '{TelemetryEnabled}'", telemetryConfigurations.TelemetryEnabled); await _telemetryConfigurationManager.SaveAsync(telemetryConfigurations, ct); } diff --git a/MSStore.CLI/Commands/Submission/DeleteCommand.cs b/MSStore.CLI/Commands/Submission/DeleteCommand.cs index 588ead6..c1ed4bf 100644 --- a/MSStore.CLI/Commands/Submission/DeleteCommand.cs +++ b/MSStore.CLI/Commands/Submission/DeleteCommand.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -20,21 +21,22 @@ internal class DeleteCommand : Command static DeleteCommand() { - NoConfirmOption = new Option( - "--no-confirm", - () => false, - "Do not prompt for confirmation."); + NoConfirmOption = new Option("--no-confirm") + { + DefaultValueFactory = _ => false, + Description = "Do not prompt for confirmation." + }; } public DeleteCommand() : base("delete", "Deletes the pending submission from the store.") { - AddArgument(SubmissionCommand.ProductIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); - AddOption(NoConfirmOption); + Options.Add(NoConfirmOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IConsoleReader consoleReader, IBrowserLauncher browserLauncher, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IConsoleReader consoleReader, IBrowserLauncher browserLauncher, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); @@ -43,23 +45,15 @@ public DeleteCommand() private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - - public bool? NoConfirm { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var noConfirm = parseResult.GetRequiredValue(NoConfirmOption); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } API.Packaged.IStorePackagedAPI storePackagedAPI = null!; @@ -70,11 +64,11 @@ public async Task InvokeAsync(InvocationContext context) { storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return null; } @@ -112,18 +106,18 @@ public async Task InvokeAsync(InvocationContext context) if (submissionId == null) { _ansiConsole.WriteLine("No pending submission found."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } _ansiConsole.WriteLine($"Found Pending Submission with Id '{submissionId}'"); - if (NoConfirm == false && !await _consoleReader.YesNoConfirmationAsync("Do you want to delete the pending submission?", ct)) + if (noConfirm == false && !await _consoleReader.YesNoConfirmationAsync("Do you want to delete the pending submission?", ct)) { return -2; } - var success = await storePackagedAPI.DeleteSubmissionAsync(_ansiConsole, ProductId, null, submissionId, _browserLauncher, _logger, ct); + var success = await storePackagedAPI.DeleteSubmissionAsync(_ansiConsole, productId, null, submissionId, _browserLauncher, _logger, ct); - return await _telemetryClient.TrackCommandEventAsync(ProductId, success ? 0 : -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, success ? 0 : -1, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/GetCommand.cs b/MSStore.CLI/Commands/Submission/GetCommand.cs index fe3698c..007f1a6 100644 --- a/MSStore.CLI/Commands/Submission/GetCommand.cs +++ b/MSStore.CLI/Commands/Submission/GetCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -18,52 +19,51 @@ namespace MSStore.CLI.Commands.Submission { internal class GetCommand : Command { + private static readonly Option ModuleOption; + + static GetCommand() + { + ModuleOption = new Option("--module", "-m") + { + Description = "Select which module you want to retrieve ('availability', 'listings' or 'properties')." + }; + } + public GetCommand() : base("get", "Retrieves the existing draft from the store submission.") { - var module = new Option( - aliases: ["--module", "-m"], - description: "Select which module you want to retrieve ('availability', 'listings' or 'properties')."); - - AddArgument(SubmissionCommand.ProductIdArgument); - AddOption(module); - AddOption(SubmissionCommand.LanguageOption); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Options.Add(ModuleOption); + Options.Add(SubmissionCommand.LanguageOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string Module { get; set; } = null!; - public string Language { get; set; } = null!; - public string ProductId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var module = parseResult.GetValue(ModuleOption); + var language = parseResult.GetRequiredValue(SubmissionCommand.LanguageOption); var submission = await _ansiConsole.Status().StartAsync("Retrieving Submission", async ctx => { try { object? submission = null; - if (ProductTypeHelper.Solve(ProductId) == ProductType.Packaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Packaged) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return -1; } @@ -75,7 +75,7 @@ public async Task InvokeAsync(InvocationContext context) try { - submission = await storeAPI.GetDraftAsync(ProductId, Module, Language, ct); + submission = await storeAPI.GetDraftAsync(productId, module, language, ct); } catch (ArgumentException ex) { @@ -112,12 +112,12 @@ public async Task InvokeAsync(InvocationContext context) if (submission == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(submission, submission.GetType(), SourceGenerationContext.GetCustom(true))); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/GetListingAssetsCommand.cs b/MSStore.CLI/Commands/Submission/GetListingAssetsCommand.cs index c22abfd..d88748b 100644 --- a/MSStore.CLI/Commands/Submission/GetListingAssetsCommand.cs +++ b/MSStore.CLI/Commands/Submission/GetListingAssetsCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,42 +22,35 @@ internal class GetListingAssetsCommand : Command public GetListingAssetsCommand() : base("getListingAssets", "Retrieves the existing draft listing assets from the store submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddOption(SubmissionCommand.LanguageOption); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Options.Add(SubmissionCommand.LanguageOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string Language { get; set; } = null!; - public string ProductId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var language = parseResult.GetRequiredValue(SubmissionCommand.LanguageOption); var ret = await _ansiConsole.Status().StartAsync("Retrieving listing assets", async ctx => { try { - if (ProductTypeHelper.Solve(ProductId) == ProductType.Packaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Packaged) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return -1; } @@ -68,7 +62,7 @@ public async Task InvokeAsync(InvocationContext context) { var storeAPI = await _storeAPIFactory.CreateAsync(ct: ct); - var draft = await storeAPI.GetDraftListingAssetsAsync(ProductId, Language, ct); + var draft = await storeAPI.GetDraftListingAssetsAsync(productId, language, ct); ctx.SuccessStatus(_ansiConsole); @@ -113,7 +107,7 @@ public async Task InvokeAsync(InvocationContext context) return await _telemetryClient.TrackCommandEventAsync(0, ct); } - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/PollCommand.cs b/MSStore.CLI/Commands/Submission/PollCommand.cs index 4a56321..d49156d 100644 --- a/MSStore.CLI/Commands/Submission/PollCommand.cs +++ b/MSStore.CLI/Commands/Submission/PollCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -22,10 +23,10 @@ internal class PollCommand : Command public PollCommand() : base("poll", "Polls until the existing submission is PUBLISHED or FAILED.") { - AddArgument(SubmissionCommand.ProductIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, TelemetryClient telemetryClient, IAnsiConsole ansiConsole, IBrowserLauncher browserLauncher) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, TelemetryClient telemetryClient, IAnsiConsole ansiConsole, IBrowserLauncher browserLauncher) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); @@ -33,16 +34,9 @@ public PollCommand() private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly IBrowserLauncher _browserLauncher = browserLauncher ?? throw new ArgumentNullException(nameof(browserLauncher)); - public string ProductId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); IStorePackagedAPI? storePackagedAPI = null; DevCenterSubmission? submission = null; @@ -51,15 +45,15 @@ public async Task InvokeAsync(InvocationContext context) { try { - if (ProductTypeHelper.Solve(ProductId) == ProductType.Packaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Packaged) { storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return -1; } @@ -67,11 +61,11 @@ public async Task InvokeAsync(InvocationContext context) if (submission?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find submission for application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find submission for application with ID '{productId}'"); return -1; } - var lastSubmissionStatus = await storePackagedAPI.PollSubmissionStatusAsync(_ansiConsole, ProductId, null, submission.Id, false, _logger, ct: ct); + var lastSubmissionStatus = await storePackagedAPI.PollSubmissionStatusAsync(_ansiConsole, productId, null, submission.Id, false, _logger, ct: ct); ctx.SuccessStatus(_ansiConsole); @@ -81,18 +75,18 @@ public async Task InvokeAsync(InvocationContext context) { var storeAPI = await _storeAPIFactory.CreateAsync(ct: ct); - var status = await storeAPI.GetModuleStatusAsync(ProductId, ct); + var status = await storeAPI.GetModuleStatusAsync(productId, ct); if (status?.ResponseData?.OngoingSubmissionId == null || status.ResponseData.OngoingSubmissionId.Length == 0) { - ctx.ErrorStatus(_ansiConsole, $"Could not find ongoing submission for application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find ongoing submission for application with ID '{productId}'"); return null; } ResponseWrapper? lastSubmissionStatus = null; - await foreach (var submissionStatus in storeAPI.PollSubmissionStatusAsync(ProductId, status.ResponseData.OngoingSubmissionId, false, ct)) + await foreach (var submissionStatus in storeAPI.PollSubmissionStatusAsync(productId, status.ResponseData.OngoingSubmissionId, false, ct)) { _ansiConsole.MarkupLine($"Submission Status - [green]{submissionStatus.ResponseData?.PublishingStatus}[/]"); if (submissionStatus.Errors != null) @@ -129,12 +123,12 @@ public async Task InvokeAsync(InvocationContext context) if (storePackagedAPI == null || submission?.Id == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } return await _telemetryClient.TrackCommandEventAsync( - ProductId, - await storePackagedAPI.HandleLastSubmissionStatusAsync(_ansiConsole, lastSubmissionStatus, ProductId, null, submission.Id, _browserLauncher, _logger, ct), + productId, + await storePackagedAPI.HandleLastSubmissionStatusAsync(_ansiConsole, lastSubmissionStatus, productId, null, submission.Id, _browserLauncher, _logger, ct), ct); } else if (publishingStatus is ResponseWrapper lastSubmissionStatusWrapper) @@ -148,21 +142,21 @@ await storePackagedAPI.HandleLastSubmissionStatusAsync(_ansiConsole, lastSubmiss foreach (var error in lastSubmissionStatusWrapper.Errors) { _logger.LogError("Could not retrieve submission. Please try again."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } } - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } else { _ansiConsole.WriteLine("Submission commit success!"); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/PublishCommand.cs b/MSStore.CLI/Commands/Submission/PublishCommand.cs index f29beaf..7c12768 100644 --- a/MSStore.CLI/Commands/Submission/PublishCommand.cs +++ b/MSStore.CLI/Commands/Submission/PublishCommand.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -19,42 +20,35 @@ internal class PublishCommand : Command public PublishCommand() : base("publish", "Starts the submission process for the existing Draft.") { - AddArgument(SubmissionCommand.ProductIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); return await _telemetryClient.TrackCommandEventAsync( - ProductId, + productId, await _ansiConsole.Status().StartAsync("Publishing submission", async ctx => { try { - if (ProductTypeHelper.Solve(ProductId) == ProductType.Packaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Packaged) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return -1; } @@ -62,7 +56,7 @@ await _ansiConsole.Status().StartAsync("Publishing submission", async ctx => if (submission?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find submission for application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find submission for application with ID '{productId}'"); return -1; } @@ -79,7 +73,7 @@ await _ansiConsole.Status().StartAsync("Publishing submission", async ctx => return 0; } - ctx.ErrorStatus(_ansiConsole, $"Could not commit submission for application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not commit submission for application with ID '{productId}'"); _ansiConsole.MarkupLine($"[red]{submissionCommit.ToErrorMessage()}[/]"); return -1; @@ -88,7 +82,7 @@ await _ansiConsole.Status().StartAsync("Publishing submission", async ctx => { var storeAPI = await _storeAPIFactory.CreateAsync(ct: ct); - var submissionId = await storeAPI.PublishSubmissionAsync(ProductId, ct); + var submissionId = await storeAPI.PublishSubmissionAsync(productId, ct); if (submissionId == null) { diff --git a/MSStore.CLI/Commands/Submission/Rollout/FinalizeCommand.cs b/MSStore.CLI/Commands/Submission/Rollout/FinalizeCommand.cs index 1c840a3..b7c8763 100644 --- a/MSStore.CLI/Commands/Submission/Rollout/FinalizeCommand.cs +++ b/MSStore.CLI/Commands/Submission/Rollout/FinalizeCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,33 +22,26 @@ internal class FinalizeCommand : Command public FinalizeCommand() : base("finalize", "Finalizes the rollout of a submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddOption(GetCommand.SubmissionIdOption); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Options.Add(GetCommand.SubmissionIdOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string? SubmissionId { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var submissionId = parseResult.GetValue(GetCommand.SubmissionIdOption); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var submissionRollout = await _ansiConsole.Status().StartAsync("Finalizing Submission Rollout", async ctx => @@ -56,26 +50,26 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - if (SubmissionId == null) + if (submissionId == null) { - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return null; } - SubmissionId = application.GetAnySubmissionId(); + submissionId = application.GetAnySubmissionId(); - if (SubmissionId == null) + if (submissionId == null) { ctx.ErrorStatus(_ansiConsole, "Could not find the submission. Please check the ProductId."); return null; } } - return await storePackagedAPI.FinalizePackageRolloutAsync(ProductId, SubmissionId, null, ct); + return await storePackagedAPI.FinalizePackageRolloutAsync(productId, submissionId, null, ct); } catch (MSStoreHttpException err) { @@ -102,12 +96,12 @@ public async Task InvokeAsync(InvocationContext context) if (submissionRollout == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/Rollout/GetCommand.cs b/MSStore.CLI/Commands/Submission/Rollout/GetCommand.cs index 229e81f..d633309 100644 --- a/MSStore.CLI/Commands/Submission/Rollout/GetCommand.cs +++ b/MSStore.CLI/Commands/Submission/Rollout/GetCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -22,41 +23,35 @@ internal class GetCommand : Command static GetCommand() { - SubmissionIdOption = new Option( - aliases: ["--submissionId", "-s"], - description: "The submission ID."); + SubmissionIdOption = new Option("--submissionId", "-s") + { + Description = "The submission ID." + }; } public GetCommand() : base("get", "Retrieves the rollout status of a submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddOption(SubmissionIdOption); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Options.Add(SubmissionIdOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string? SubmissionId { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var submissionId = parseResult.GetValue(SubmissionIdOption); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var submissionRollout = await _ansiConsole.Status().StartAsync("Retrieving Submission Rollout", async ctx => @@ -65,26 +60,26 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - if (SubmissionId == null) + if (submissionId == null) { - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return null; } - SubmissionId = application.GetAnySubmissionId(); + submissionId = application.GetAnySubmissionId(); - if (SubmissionId == null) + if (submissionId == null) { ctx.ErrorStatus(_ansiConsole, "Could not find the submission. Please check the ProductId."); return null; } } - return await storePackagedAPI.GetPackageRolloutAsync(ProductId, SubmissionId, null, ct); + return await storePackagedAPI.GetPackageRolloutAsync(productId, submissionId, null, ct); } catch (MSStoreHttpException err) { @@ -111,12 +106,12 @@ public async Task InvokeAsync(InvocationContext context) if (submissionRollout == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/Rollout/HaltCommand.cs b/MSStore.CLI/Commands/Submission/Rollout/HaltCommand.cs index 596ad5b..3ea9bc7 100644 --- a/MSStore.CLI/Commands/Submission/Rollout/HaltCommand.cs +++ b/MSStore.CLI/Commands/Submission/Rollout/HaltCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,33 +22,26 @@ internal class HaltCommand : Command public HaltCommand() : base("halt", "Halts the rollout of a submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddOption(GetCommand.SubmissionIdOption); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Options.Add(GetCommand.SubmissionIdOption); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string? SubmissionId { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var submissionId = parseResult.GetValue(GetCommand.SubmissionIdOption); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var submissionRollout = await _ansiConsole.Status().StartAsync("Halting Submission Rollout", async ctx => @@ -56,26 +50,26 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - if (SubmissionId == null) + if (submissionId == null) { - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return null; } - SubmissionId = application.GetAnySubmissionId(); + submissionId = application.GetAnySubmissionId(); - if (SubmissionId == null) + if (submissionId == null) { ctx.ErrorStatus(_ansiConsole, "Could not find the submission. Please check the ProductId."); return null; } } - return await storePackagedAPI.HaltPackageRolloutAsync(ProductId, SubmissionId, null, ct); + return await storePackagedAPI.HaltPackageRolloutAsync(productId, submissionId, null, ct); } catch (MSStoreHttpException err) { @@ -102,12 +96,12 @@ public async Task InvokeAsync(InvocationContext context) if (submissionRollout == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/Rollout/UpdateCommand.cs b/MSStore.CLI/Commands/Submission/Rollout/UpdateCommand.cs index 0e825f0..40671cb 100644 --- a/MSStore.CLI/Commands/Submission/Rollout/UpdateCommand.cs +++ b/MSStore.CLI/Commands/Submission/Rollout/UpdateCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -18,48 +19,47 @@ namespace MSStore.CLI.Commands.Submission.Rollout { internal class UpdateCommand : Command { + private static readonly Argument PercentageArgument; + + static UpdateCommand() + { + PercentageArgument = new Argument("percentage") + { + Description = "The percentage of users that will receive the submission rollout." + }; + } + public UpdateCommand() : base("update", "Update the rollout percentage of a submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); - AddOption(GetCommand.SubmissionIdOption); - - var percentage = new Argument( - "percentage", - description: "The percentage of users that will receive the submission rollout."); - AddArgument(percentage); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Options.Add(GetCommand.SubmissionIdOption); + Arguments.Add(PercentageArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - public string? SubmissionId { get; set; } - public float Percentage { get; set; } - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var submissionId = parseResult.GetValue(GetCommand.SubmissionIdOption); + var percentage = parseResult.GetRequiredValue(PercentageArgument); - if (ProductTypeHelper.Solve(ProductId) == ProductType.Unpackaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Unpackaged) { _ansiConsole.WriteLine("This command is not supported for unpackaged applications."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } - if (Percentage < 0 || Percentage > 100) + if (percentage < 0 || percentage > 100) { _ansiConsole.WriteLine("The percentage must be between 0 and 100."); - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } var submissionRollout = await _ansiConsole.Status().StartAsync("Updating Submission Rollout", async ctx => @@ -68,26 +68,26 @@ public async Task InvokeAsync(InvocationContext context) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - if (SubmissionId == null) + if (submissionId == null) { - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return null; } - SubmissionId = application.GetAnySubmissionId(); + submissionId = application.GetAnySubmissionId(); - if (SubmissionId == null) + if (submissionId == null) { ctx.ErrorStatus(_ansiConsole, "Could not find the submission. Please check the ProductId."); return null; } } - return await storePackagedAPI.UpdatePackageRolloutPercentageAsync(ProductId, SubmissionId, null, Percentage, ct); + return await storePackagedAPI.UpdatePackageRolloutPercentageAsync(productId, submissionId, null, percentage, ct); } catch (MSStoreHttpException err) { @@ -114,12 +114,12 @@ public async Task InvokeAsync(InvocationContext context) if (submissionRollout == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(submissionRollout, SourceGenerationContext.GetCustom(true).PackageRollout)); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/RolloutCommand.cs b/MSStore.CLI/Commands/Submission/RolloutCommand.cs index 737063c..3da01df 100644 --- a/MSStore.CLI/Commands/Submission/RolloutCommand.cs +++ b/MSStore.CLI/Commands/Submission/RolloutCommand.cs @@ -7,14 +7,13 @@ namespace MSStore.CLI.Commands.Submission { internal class RolloutCommand : Command { - public RolloutCommand() + public RolloutCommand(Rollout.GetCommand getCommand, Rollout.UpdateCommand updateCommand, Rollout.HaltCommand haltCommand, Rollout.FinalizeCommand finalizeCommand) : base("rollout", "Execute rollout related operations") { - AddCommand(new Rollout.GetCommand()); - AddCommand(new Rollout.UpdateCommand()); - AddCommand(new Rollout.HaltCommand()); - AddCommand(new Rollout.FinalizeCommand()); - this.SetDefaultHelpHandler(); + Subcommands.Add(getCommand); + Subcommands.Add(updateCommand); + Subcommands.Add(haltCommand); + Subcommands.Add(finalizeCommand); } } } diff --git a/MSStore.CLI/Commands/Submission/StatusCommand.cs b/MSStore.CLI/Commands/Submission/StatusCommand.cs index c01d48e..8799c63 100644 --- a/MSStore.CLI/Commands/Submission/StatusCommand.cs +++ b/MSStore.CLI/Commands/Submission/StatusCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -21,40 +22,33 @@ internal class StatusCommand : Command public StatusCommand() : base("status", "Retrieves the current status of the store submission.") { - AddArgument(SubmissionCommand.ProductIdArgument); + Arguments.Add(SubmissionCommand.ProductIdArgument); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string ProductId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); var status = await _ansiConsole.Status().StartAsync("Retrieving submission status", async ctx => { try { - if (ProductTypeHelper.Solve(ProductId) == ProductType.Packaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Packaged) { var storePackagedAPI = await _storeAPIFactory.CreatePackagedAsync(ct: ct); - var application = await storePackagedAPI.GetApplicationAsync(ProductId, ct); + var application = await storePackagedAPI.GetApplicationAsync(productId, ct); if (application?.Id == null) { - ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{ProductId}'"); + ctx.ErrorStatus(_ansiConsole, $"Could not find application with ID '{productId}'"); return null; } @@ -64,7 +58,7 @@ public async Task InvokeAsync(InvocationContext context) { var storeAPI = await _storeAPIFactory.CreateAsync(ct: ct); - return await storeAPI.GetModuleStatusAsync(ProductId, ct); + return await storeAPI.GetModuleStatusAsync(productId, ct); } } catch (Exception err) @@ -77,7 +71,7 @@ public async Task InvokeAsync(InvocationContext context) if (status == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } if (status is DevCenterSubmission devCenterSubmission && devCenterSubmission.Id != null) @@ -87,14 +81,14 @@ public async Task InvokeAsync(InvocationContext context) _ansiConsole.MarkupLine($"Submission Status = [green]{devCenterSubmission.Status}[/]"); } - devCenterSubmission.StatusDetails?.PrintAllTables(_ansiConsole, ProductId, devCenterSubmission.Id, _logger); + devCenterSubmission.StatusDetails?.PrintAllTables(_ansiConsole, productId, devCenterSubmission.Id, _logger); } else { AnsiConsole.WriteLine(JsonSerializer.Serialize(status, status.GetType(), SourceGenerationContext.GetCustom(true))); } - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/UpdateCommand.cs b/MSStore.CLI/Commands/Submission/UpdateCommand.cs index 62581a7..aeed25f 100644 --- a/MSStore.CLI/Commands/Submission/UpdateCommand.cs +++ b/MSStore.CLI/Commands/Submission/UpdateCommand.cs @@ -20,29 +20,31 @@ namespace MSStore.CLI.Commands.Submission { internal class UpdateCommand : Command { + private static readonly Argument ProductArgument; + + static UpdateCommand() + { + ProductArgument = new Argument("product") + { + Description = "The updated JSON product representation." + }; + } + public UpdateCommand() : base("update", "Updates the existing draft with the provided JSON.") { - var product = new Argument( - "product", - description: "The updated JSON product representation."); - - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(product); - AddOption(SubmissionCommand.SkipInitialPolling); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(ProductArgument); + Options.Add(SubmissionCommand.SkipInitialPolling); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string Product { get; set; } = null!; - public bool SkipInitialPolling { get; set; } - public string ProductId { get; set; } = null!; - public static async Task PackagedUpdateCommandAsync(IAnsiConsole ansiConsole, IStoreAPIFactory storeAPIFactory, string product, string productId, ILogger logger, CancellationToken ct) { var updateSubmission = JsonSerializer.Deserialize(product, SourceGenerationContext.GetCustom().DevCenterSubmission); @@ -112,24 +114,21 @@ public UpdateCommand() }); } - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var product = parseResult.GetRequiredValue(ProductArgument); + var skipInitialPolling = parseResult.GetRequiredValue(SubmissionCommand.SkipInitialPolling); object? updateSubmissionData = null; - if (ProductTypeHelper.Solve(ProductId) == ProductType.Packaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Packaged) { - updateSubmissionData = await PackagedUpdateCommandAsync(_ansiConsole, _storeAPIFactory, Product, ProductId, _logger, ct); + updateSubmissionData = await PackagedUpdateCommandAsync(_ansiConsole, _storeAPIFactory, product, productId, _logger, ct); } else { - var updatePackagesRequest = JsonSerializer.Deserialize(Product, SourceGenerationContext.GetCustom().UpdatePackagesRequest); + var updatePackagesRequest = JsonSerializer.Deserialize(product, SourceGenerationContext.GetCustom().UpdatePackagesRequest); if (updatePackagesRequest == null) { @@ -142,7 +141,7 @@ public async Task InvokeAsync(InvocationContext context) { var storeAPI = await _storeAPIFactory.CreateAsync(ct: ct); - return await storeAPI.UpdateProductPackagesAsync(ProductId, updatePackagesRequest, SkipInitialPolling, ct); + return await storeAPI.UpdateProductPackagesAsync(productId, updatePackagesRequest, skipInitialPolling, ct); } catch (Exception err) { @@ -155,12 +154,12 @@ public async Task InvokeAsync(InvocationContext context) if (updateSubmissionData == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(updateSubmissionData, updateSubmissionData.GetType(), SourceGenerationContext.GetCustom(true))); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/Submission/UpdateMetadataCommand.cs b/MSStore.CLI/Commands/Submission/UpdateMetadataCommand.cs index 890296d..c89fa6e 100644 --- a/MSStore.CLI/Commands/Submission/UpdateMetadataCommand.cs +++ b/MSStore.CLI/Commands/Submission/UpdateMetadataCommand.cs @@ -5,6 +5,7 @@ using System.CommandLine; using System.CommandLine.Invocation; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; @@ -18,47 +19,46 @@ namespace MSStore.CLI.Commands.Submission { internal class UpdateMetadataCommand : Command { + private static readonly Argument MetadataArgument; + + static UpdateMetadataCommand() + { + MetadataArgument = new Argument("metadata") + { + Description = "The updated JSON metadata representation." + }; + } + public UpdateMetadataCommand() : base("updateMetadata", "Updates the existing draft submission metadata with the provided JSON.") { - var metadata = new Argument( - name: "metadata", - description: "The updated JSON metadata representation."); - - AddArgument(SubmissionCommand.ProductIdArgument); - AddArgument(metadata); - AddOption(SubmissionCommand.SkipInitialPolling); + Arguments.Add(SubmissionCommand.ProductIdArgument); + Arguments.Add(MetadataArgument); + Options.Add(SubmissionCommand.SkipInitialPolling); } - public new class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(ILogger logger, IStoreAPIFactory storeAPIFactory, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private readonly IStoreAPIFactory _storeAPIFactory = storeAPIFactory ?? throw new ArgumentNullException(nameof(storeAPIFactory)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public string Metadata { get; set; } = null!; - public bool SkipInitialPolling { get; set; } - public string ProductId { get; set; } = null!; - - public int Invoke(InvocationContext context) - { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - var ct = context.GetCancellationToken(); + var productId = parseResult.GetRequiredValue(SubmissionCommand.ProductIdArgument); + var metadata = parseResult.GetRequiredValue(MetadataArgument); + var skipInitialPolling = parseResult.GetRequiredValue(SubmissionCommand.SkipInitialPolling); object? updateSubmissionData = null; - if (ProductTypeHelper.Solve(ProductId) == ProductType.Packaged) + if (ProductTypeHelper.Solve(productId) == ProductType.Packaged) { - updateSubmissionData = await UpdateCommand.Handler.PackagedUpdateCommandAsync(_ansiConsole, _storeAPIFactory, Metadata, ProductId, _logger, ct); + updateSubmissionData = await UpdateCommand.Handler.PackagedUpdateCommandAsync(_ansiConsole, _storeAPIFactory, metadata, productId, _logger, ct); } else { - var submissionMetadata = JsonSerializer.Deserialize(Metadata, SourceGenerationContext.GetCustom().UpdateMetadataRequest); + var submissionMetadata = JsonSerializer.Deserialize(metadata, SourceGenerationContext.GetCustom().UpdateMetadataRequest); if (submissionMetadata == null) { @@ -71,7 +71,7 @@ public async Task InvokeAsync(InvocationContext context) { var storeAPI = await _storeAPIFactory.CreateAsync(ct: ct); - return await storeAPI.UpdateSubmissionMetadataAsync(ProductId, submissionMetadata, SkipInitialPolling, ct); + return await storeAPI.UpdateSubmissionMetadataAsync(productId, submissionMetadata, skipInitialPolling, ct); } catch (Exception err) { @@ -84,12 +84,12 @@ public async Task InvokeAsync(InvocationContext context) if (updateSubmissionData == null) { - return await _telemetryClient.TrackCommandEventAsync(ProductId, -1, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, -1, ct); } AnsiConsole.WriteLine(JsonSerializer.Serialize(updateSubmissionData, updateSubmissionData.GetType(), SourceGenerationContext.GetCustom(true))); - return await _telemetryClient.TrackCommandEventAsync(ProductId, 0, ct); + return await _telemetryClient.TrackCommandEventAsync(productId, 0, ct); } } } diff --git a/MSStore.CLI/Commands/SubmissionCommand.cs b/MSStore.CLI/Commands/SubmissionCommand.cs index ef84520..17d2de5 100644 --- a/MSStore.CLI/Commands/SubmissionCommand.cs +++ b/MSStore.CLI/Commands/SubmissionCommand.cs @@ -14,32 +14,35 @@ internal class SubmissionCommand : Command static SubmissionCommand() { - LanguageOption = new Option( - aliases: ["--language", "-l"], - getDefaultValue: () => "en", - description: "Select which language you want to retrieve."); - SkipInitialPolling = new Option( - aliases: ["--skipInitialPolling", "-s"], - getDefaultValue: () => false, - description: "Skip the initial polling before executing the action."); - ProductIdArgument = new Argument( - name: "productId", - description: "The product ID."); + LanguageOption = new Option("--language", "-l") + { + DefaultValueFactory = _ => "en", + Description = "Select which language you want to retrieve." + }; + + SkipInitialPolling = new Option("--skipInitialPolling", "-s") + { + DefaultValueFactory = _ => false, + Description = "Skip the initial polling before executing the action." + }; + ProductIdArgument = new Argument("productId") + { + Description = "The product ID." + }; } - public SubmissionCommand() + public SubmissionCommand(StatusCommand statusCommand, GetCommand getCommand, GetListingAssetsCommand getListingAssetsCommand, UpdateMetadataCommand updateMetadataCommand, UpdateCommand updateCommand, PollCommand pollCommand, Submission.PublishCommand publishCommand, DeleteCommand deleteCommand, RolloutCommand rolloutCommand) : base("submission", "Executes commands to a store submission.") { - AddCommand(new StatusCommand()); - AddCommand(new GetCommand()); - AddCommand(new GetListingAssetsCommand()); - AddCommand(new UpdateMetadataCommand()); - AddCommand(new UpdateCommand()); - AddCommand(new PollCommand()); - AddCommand(new Submission.PublishCommand()); - AddCommand(new DeleteCommand()); - AddCommand(new RolloutCommand()); - this.SetDefaultHelpHandler(); + Subcommands.Add(statusCommand); + Subcommands.Add(getCommand); + Subcommands.Add(getListingAssetsCommand); + Subcommands.Add(updateMetadataCommand); + Subcommands.Add(updateCommand); + Subcommands.Add(pollCommand); + Subcommands.Add(publishCommand); + Subcommands.Add(deleteCommand); + Subcommands.Add(rolloutCommand); } } } diff --git a/MSStore.CLI/Helpers/ParseResultExtensions.cs b/MSStore.CLI/Helpers/ParseResultExtensions.cs index a9efe03..70b2635 100644 --- a/MSStore.CLI/Helpers/ParseResultExtensions.cs +++ b/MSStore.CLI/Helpers/ParseResultExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.CommandLine.Parsing; +using System.CommandLine; namespace MSStore.CLI.Helpers { @@ -10,7 +10,7 @@ internal static class ParseResultExtensions public static bool IsVerbose(this ParseResult parseResult) { return parseResult.RootCommandResult.Command is MicrosoftStoreCLI storeCLI && - parseResult.GetValueForOption(storeCLI.VerboseOption); + parseResult.GetValue(MicrosoftStoreCLI.VerboseOption); } } } diff --git a/MSStore.CLI/Helpers/TelemetryHelper.cs b/MSStore.CLI/Helpers/TelemetryHelper.cs index baed9b7..11c1bfc 100644 --- a/MSStore.CLI/Helpers/TelemetryHelper.cs +++ b/MSStore.CLI/Helpers/TelemetryHelper.cs @@ -35,7 +35,7 @@ public static Task TrackCommandEventAsync(this TelemetryClient telemetryCli } public static Task TrackCommandEventAsync(this TelemetryClient telemetryClient, int returnCode, IDictionary? properties = null, CancellationToken ct = default) - where T : ICommandHandler + where T : AsynchronousCommandLineAction { var typeName = typeof(T).FullName; if (typeName == null) @@ -66,13 +66,13 @@ public static Task TrackCommandEventAsync(this TelemetryClient telemetry } public static Task TrackCommandEventAsync(this TelemetryClient telemetryClient, int returnCode, CancellationToken ct = default) - where T : ICommandHandler + where T : AsynchronousCommandLineAction { return TrackCommandEventAsync(telemetryClient, returnCode, null, ct); } public static Task TrackCommandEventAsync(this TelemetryClient telemetryClient, string productId, int returnCode, IDictionary? properties = null, CancellationToken ct = default) - where T : ICommandHandler + where T : AsynchronousCommandLineAction { properties ??= new Dictionary(); @@ -82,7 +82,7 @@ public static Task TrackCommandEventAsync(this TelemetryClient telemetry } public static Task TrackCommandEventAsync(this TelemetryClient telemetryClient, string productId, int returnCode, CancellationToken ct = default) - where T : ICommandHandler + where T : AsynchronousCommandLineAction { return TrackCommandEventAsync(telemetryClient, productId, returnCode, null, ct); } diff --git a/MSStore.CLI/MSStore.CLI.csproj b/MSStore.CLI/MSStore.CLI.csproj index 6d9934e..5894efc 100644 --- a/MSStore.CLI/MSStore.CLI.csproj +++ b/MSStore.CLI/MSStore.CLI.csproj @@ -33,16 +33,15 @@ - - - - - - - + + + + + + + - - + diff --git a/MSStore.CLI/MicrosoftStoreCLI.cs b/MSStore.CLI/MicrosoftStoreCLI.cs index 3d3e8d4..5839348 100644 --- a/MSStore.CLI/MicrosoftStoreCLI.cs +++ b/MSStore.CLI/MicrosoftStoreCLI.cs @@ -19,6 +19,17 @@ namespace MSStore.CLI { internal class MicrosoftStoreCLI : RootCommand { + internal static Option VerboseOption { get; } + + static MicrosoftStoreCLI() + { + VerboseOption = new Option("--verbose", "-v") + { + DefaultValueFactory = _ => false, + Description = "Verbose output" + }; + } + internal static void WelcomeMessage(IAnsiConsole ansiConsole) { ansiConsole.WriteLine(); @@ -28,48 +39,43 @@ internal static void WelcomeMessage(IAnsiConsole ansiConsole) ansiConsole.WriteLine(); } - internal Option VerboseOption { get; } - - public MicrosoftStoreCLI() + public MicrosoftStoreCLI(InfoCommand infoCommand, ReconfigureCommand reconfigureCommand, SettingsCommand settingsCommand, AppsCommand appsCommand, SubmissionCommand submissionCommand, FlightsCommand flightsCommand, InitCommand initCommand, PackageCommand packageCommand, PublishCommand publishCommand, Handler handler) : base(description: "CLI tool to automate Microsoft Store Developer tasks.") { - VerboseOption = new Option( - aliases: ["--verbose", "-v"], - getDefaultValue: () => false, - description: "Verbose output"); - AddGlobalOption(VerboseOption); - - AddCommand(new InfoCommand()); - AddCommand(new ReconfigureCommand()); - AddCommand(new SettingsCommand()); - AddCommand(new AppsCommand()); - AddCommand(new SubmissionCommand()); - AddCommand(new FlightsCommand()); - AddCommand(new InitCommand()); - AddCommand(new PackageCommand()); - AddCommand(new PublishCommand()); - - this.SetHandler(() => + Subcommands.Add(infoCommand); + Subcommands.Add(reconfigureCommand); + Subcommands.Add(settingsCommand); + Subcommands.Add(appsCommand); + Subcommands.Add(submissionCommand); + Subcommands.Add(flightsCommand); + Subcommands.Add(initCommand); + Subcommands.Add(packageCommand); + Subcommands.Add(publishCommand); + + SetAction((parseResult, ct) => { + foreach (var option in Options) + { + if (option is HelpOption defaultHelpOption && defaultHelpOption.Action is HelpAction helpAction) + { + helpAction.Invoke(parseResult); + return Task.CompletedTask; + } + } + + return handler.InvokeAsync(parseResult, ct); }); } - public new class Handler(IConfigurationManager configurationManager, ICLIConfigurator cliConfigurator, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : ICommandHandler + public class Handler(IConfigurationManager configurationManager, ICLIConfigurator cliConfigurator, IAnsiConsole ansiConsole, TelemetryClient telemetryClient) : AsynchronousCommandLineAction { private readonly IConfigurationManager _configurationManager = configurationManager ?? throw new ArgumentNullException(nameof(configurationManager)); private readonly ICLIConfigurator _cliConfigurator = cliConfigurator ?? throw new ArgumentNullException(nameof(cliConfigurator)); private readonly IAnsiConsole _ansiConsole = ansiConsole ?? throw new ArgumentNullException(nameof(ansiConsole)); private readonly TelemetryClient _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); - public int Invoke(InvocationContext context) + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken ct = default) { - return -1001; - } - - public async Task InvokeAsync(InvocationContext context) - { - var ct = context.GetCancellationToken(); - var config = await _configurationManager.LoadAsync(ct: ct); if (config.SellerId == null) @@ -81,8 +87,15 @@ await _cliConfigurator.ConfigureAsync(_ansiConsole, false, ct: ct) ? 0 : -1, } else { - HelpBuilder helpBuilder = new(LocalizationResources.Instance, CommandExtensions.GetBufferWidth()); - helpBuilder.Write(context.ParseResult.RootCommandResult.Command, Console.Out); + foreach (var option in parseResult.RootCommandResult.Command.Options) + { + if (option is HelpOption defaultHelpOption && defaultHelpOption.Action is HelpAction helpAction) + { + helpAction.Invoke(parseResult); + break; + } + } + _ansiConsole.MarkupLine("Use of the Microsoft Store Developer CLI is subject to the terms of the Microsoft Privacy Statement: [link]https://aka.ms/privacy[/]"); } diff --git a/MSStore.CLI/Program.cs b/MSStore.CLI/Program.cs index 402914d..a815f6b 100644 --- a/MSStore.CLI/Program.cs +++ b/MSStore.CLI/Program.cs @@ -2,10 +2,7 @@ // Licensed under the MIT License. using System; -using System.CommandLine.Builder; -using System.CommandLine.Hosting; using System.CommandLine.Invocation; -using System.CommandLine.Parsing; using System.Diagnostics; using System.IO; using System.Linq; @@ -44,8 +41,6 @@ public static async Task Main(params string[] args) Console.OutputEncoding = System.Text.Encoding.UTF8; #endif - var storeCLI = new MicrosoftStoreCLI(); - var minimumLogLevel = LogLevel.Critical; var telemetryConfigurationManager = new ConfigurationManager( @@ -60,8 +55,13 @@ public static async Task Main(params string[] args) Out = new AnsiConsoleOutput(Console.Error) }); - var builder = new CommandLineBuilder(storeCLI); - var parser = builder.UseHost(_ => Host.CreateDefaultBuilder(args), (builder) => builder + if (args.Contains(MicrosoftStoreCLI.VerboseOption.Name) || args.Any(MicrosoftStoreCLI.VerboseOption.Aliases.Contains)) + { + minimumLogLevel = LogLevel.Information; + } + + var hostBuilder = Host.CreateDefaultBuilder(args) + .UseConsoleLifetime() .UseEnvironment("CLI") .ConfigureServices((hostContext, services) => { @@ -213,45 +213,11 @@ void AddMSCorrelationId(HttpRequestHeaders defaultRequestHeaders) .ConfigureLogging((hostContext, logging) => { logging.SetMinimumLevel(minimumLogLevel); - })) - .AddMiddleware( - async (context, next) => - { - var ct = context.GetCancellationToken(); + }); - var host = context.GetHost(); + IHost host = hostBuilder.Start(); - var configurationManager = host.Services.GetService>()!; - var credentialManager = host.Services.GetService()!; - var consoleReader = host.Services.GetService()!; - var cliConfigurator = host.Services.GetService()!; - var logger = host.Services.GetService>()!; - - logger.LogInformation("Command is {Command}", context.ParseResult.CommandResult.Command.Name); - - if (context.ParseResult.CommandResult.Command is MicrosoftStoreCLI - || context.ParseResult.CommandResult.Command is ReconfigureCommand - || await MicrosoftStoreCLI.InitAsync(ansiConsole, configurationManager, credentialManager, consoleReader, cliConfigurator, logger, ct)) - { - await next(context); - } - }, MiddlewareOrder.Default) - .UseVersionOption() - .UseEnvironmentVariableDirective() - .UseParseDirective() - .UseSuggestDirective() - .RegisterWithDotnetSuggest() - .UseTypoCorrections() - .UseParseErrorReporting() - .UseExceptionHandler() - .UseHelp() - .CancelOnProcessTermination() - .Build(); - - if (parser.Parse(args).IsVerbose()) - { - minimumLogLevel = LogLevel.Information; - } + IHostApplicationLifetime lifetime = host.Services.GetRequiredService(); var argList = args.ToList(); @@ -275,7 +241,30 @@ void AddMSCorrelationId(HttpRequestHeaders defaultRequestHeaders) args = [.. argList]; - var result = await parser.InvokeAsync(args); + var storeCLI = host.Services.GetRequiredService(); + var parseResult = storeCLI.Parse(args); + + var logger = host.Services.GetService>()!; + logger.LogInformation("Command is {Command}", parseResult.CommandResult.Command.Name); + + if (parseResult.CommandResult.Command is not MicrosoftStoreCLI + && parseResult.CommandResult.Command is not ReconfigureCommand + && !await MicrosoftStoreCLI.InitAsync(ansiConsole, host.Services.GetService>()!, host.Services.GetService()!, host.Services.GetService()!, host.Services.GetService()!, logger, lifetime.ApplicationStopping)) + { + // Initialization failed + await host.StopAsync(); + return -1; + } + + if (parseResult.Action is ParseErrorAction parseError) + { + parseError.ShowTypoCorrections = true; + parseError.ShowHelp = true; + } + + var result = await parseResult.InvokeAsync(parseResult.InvocationConfiguration, lifetime.ApplicationStopping); + + await host.StopAsync(); await telemetryClient.FlushAsync(CancellationToken.None); diff --git a/MSStore.CLI/StoreHostBuilderExtensions.cs b/MSStore.CLI/StoreHostBuilderExtensions.cs index 531d0f8..8670b3f 100644 --- a/MSStore.CLI/StoreHostBuilderExtensions.cs +++ b/MSStore.CLI/StoreHostBuilderExtensions.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.CommandLine.Hosting; +using System.CommandLine; +using System.CommandLine.Invocation; using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using MSStore.CLI.Commands; @@ -10,7 +12,6 @@ namespace MSStore.CLI { internal static class StoreHostBuilderExtensions { - // IL Trimming, until System.CommandLine.Hosting supports it [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(InitCommand.Handler))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(InfoCommand.Handler))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ReconfigureCommand.Handler))] @@ -50,42 +51,79 @@ internal static class StoreHostBuilderExtensions public static IHostBuilder ConfigureStoreCLICommands(this IHostBuilder builder) { return builder - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler() - .UseCommandHandler(); + .ConfigureServices(services => + { + services + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .ConfigureCommand() + .UseCommandHandler() + .UseCommandHandler() + .ConfigureCommand() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .ConfigureCommand() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .ConfigureCommand() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .ConfigureCommand() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .ConfigureCommand() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler() + .UseCommandHandler(); + }); + } + + public static IServiceCollection UseCommandHandler<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommand, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>(this IServiceCollection services) + where TCommand : Command + where THandler : AsynchronousCommandLineAction + { + return services + .AddSingleton() + .AddSingleton(sp => + { + var command = ActivatorUtilities.CreateInstance(sp); + command.Options.Add(MicrosoftStoreCLI.VerboseOption); + command.SetAction((parseResult, ct) => sp.GetRequiredService().InvokeAsync(parseResult, ct)); + return command; + }); + } + + public static IServiceCollection ConfigureCommand<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TCommand>(this IServiceCollection services) + where TCommand : Command + { + return services + .AddSingleton(sp => + { + var command = ActivatorUtilities.CreateInstance(sp); + command.Options.Add(MicrosoftStoreCLI.VerboseOption); + return command; + }); } } }