| File: Commands\TemplateManagementCommand.cs | Web Access |
| Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; using System.Globalization; using System.Text.Json; using System.Text.Json.Nodes; using Aspire.Cli.Interaction; using Aspire.Cli.Resources; using Aspire.Cli.Templating; using Aspire.Cli.Utils; using Spectre.Console; namespace Aspire.Cli.Commands; internal sealed class TemplateManagementCommand : ParentCommand { internal override HelpGroup HelpGroup => HelpGroup.ToolsAndConfiguration; public TemplateManagementCommand(TemplateSourceStore store, CommonCommandServices services) : base("template", NewCommandStrings.TemplateCommandDescription, services) { Subcommands.Add(new SourceCommand(store, services)); } internal sealed record AddSourceOptions(string Name, string? Path, string? Repository, string? Ref) { internal static Argument<string> NameArgument { get; } = new("name") { Description = NewCommandStrings.TemplateSourceNameDescription }; internal static Option<string?> PathOption { get; } = new("--path") { Description = NewCommandStrings.TemplateSourcePathDescription }; internal static Option<string?> RepositoryOption { get; } = new("--repository") { Description = NewCommandStrings.TemplateSourceRepositoryDescription }; internal static Option<string?> RefOption { get; } = new("--ref") { Description = NewCommandStrings.TemplateSourceRefDescription }; public static AddSourceOptions FromParseResult(ParseResult parseResult) { var options = new AddSourceOptions( parseResult.GetRequiredValue(NameArgument), parseResult.GetValue(PathOption), parseResult.GetValue(RepositoryOption), parseResult.GetValue(RefOption)); options.Validate(); return options; } public JsonObject ToJsonObject() { JsonObject source = new(); foreach (var (field, value) in new[] { ("path", Path), ("repository", Repository), ("ref", Ref) }) { if (value is not null) { source[field] = value; } } return source; } private void Validate() { TemplateSourceConfiguration.ValidateName(Name); TemplateSourceConfiguration.Parse(Path, Repository, Ref, $"templateSources.{Name}", requireComplete: true); } } private sealed class SourceCommand : ParentCommand { public SourceCommand(TemplateSourceStore store, CommonCommandServices services) : base("source", NewCommandStrings.TemplateSourceCommandDescription, services) { Subcommands.Add(new AddSourceCommand(store, services)); Subcommands.Add(new RemoveSourceCommand(store, services)); Subcommands.Add(new ListSourcesCommand(store, services)); } } private sealed class ListSourcesCommand : BaseCommand { private readonly Option<OutputFormat> _format = new("--format") { Description = SharedCommandStrings.LsFormatOptionDescription }; private readonly TemplateSourceStore _store; public ListSourcesCommand(TemplateSourceStore store, CommonCommandServices services) : base("list", NewCommandStrings.TemplateSourceListDescription, services) { _store = store; Options.Add(_format); } protected override async Task<CommandResult> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken) { JsonObject sources; try { sources = await _store.ListAsync(cancellationToken); } catch (TemplateSourceConfigurationException ex) { return CommandResult.Failure(CliExitCodes.InvalidCommand, ex.Message); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException) { return CommandResult.Failure(CliExitCodes.InvalidCommand, NewCommandStrings.TemplateSourceReadFailed); } var orderedSources = sources.OrderBy(source => source.Key, StringComparers.TemplateSourceName) .ThenBy(source => source.Key, StringComparer.Ordinal); if (parseResult.GetValue(_format) == OutputFormat.Json) { JsonObject result = new(); foreach (var (name, source) in orderedSources) { result[name] = source?.DeepClone(); } InteractionService.DisplayRawText(JsonSerializer.Serialize(result, JsonSourceGenerationContext.Default.JsonObject), ConsoleOutput.Standard); } else if (sources.Count == 0) { InteractionService.DisplayMessage(KnownEmojis.Information, NewCommandStrings.TemplateSourceListEmpty); } else { var table = new Table() .AddBoldColumn(NewCommandStrings.TemplateSourceNameHeader) .AddBoldColumn(NewCommandStrings.TemplateSourceConfigurationHeader); foreach (var (name, source) in orderedSources) { var configuration = source is JsonObject definition ? string.Join(Environment.NewLine, definition.Select(field => $"{field.Key}: {field.Value?.ToString() ?? "null"}")) : source?.ToJsonString() ?? "null"; table.AddRow(name.EscapeMarkup(), string.IsNullOrEmpty(configuration) ? "-" : configuration.EscapeMarkup()); } InteractionService.DisplayRenderable(table); } return CommandResult.Success(); } } private sealed class AddSourceCommand : BaseCommand { private readonly TemplateSourceStore _store; public AddSourceCommand(TemplateSourceStore store, CommonCommandServices services) : base("add", NewCommandStrings.TemplateSourceAddDescription, services) { _store = store; Arguments.Add(AddSourceOptions.NameArgument); Options.Add(AddSourceOptions.PathOption); Options.Add(AddSourceOptions.RepositoryOption); Options.Add(AddSourceOptions.RefOption); } protected override async Task<CommandResult> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken) { try { var options = AddSourceOptions.FromParseResult(parseResult); await _store.AddAsync(options.Name, options.ToJsonObject(), cancellationToken); InteractionService.DisplaySuccess(string.Format(CultureInfo.CurrentCulture, NewCommandStrings.TemplateSourceAdded, options.Name)); return CommandResult.Success(); } catch (TemplateSourceConfigurationException ex) { return CommandResult.Failure(CliExitCodes.InvalidCommand, ex.Message); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException) { return CommandResult.Failure(CliExitCodes.InvalidCommand, NewCommandStrings.TemplateSourceWriteFailed); } } } private sealed class RemoveSourceCommand : BaseCommand { private readonly Argument<string> _name = new("name") { Description = NewCommandStrings.TemplateSourceNameDescription }; private readonly TemplateSourceStore _store; public RemoveSourceCommand(TemplateSourceStore store, CommonCommandServices services) : base("remove", NewCommandStrings.TemplateSourceRemoveDescription, services) { _store = store; Arguments.Add(_name); } protected override async Task<CommandResult> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken) { var name = parseResult.GetRequiredValue(_name); try { if (!await _store.RemoveAsync(name, cancellationToken)) { return CommandResult.Failure(CliExitCodes.ConfigNotFound, string.Format(CultureInfo.CurrentCulture, NewCommandStrings.TemplateSourceNotFound, name)); } InteractionService.DisplaySuccess(string.Format(CultureInfo.CurrentCulture, NewCommandStrings.TemplateSourceRemoved, name)); return CommandResult.Success(); } catch (TemplateSourceConfigurationException ex) { return CommandResult.Failure(CliExitCodes.InvalidCommand, ex.Message); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException) { return CommandResult.Failure(CliExitCodes.InvalidCommand, NewCommandStrings.TemplateSourceWriteFailed); } } } }