diff --git a/src/Nox.Cli.Abstractions/Configuration/IJobConfiguration.cs b/src/Nox.Cli.Abstractions/Configuration/IJobConfiguration.cs new file mode 100644 index 00000000..c21dc5e0 --- /dev/null +++ b/src/Nox.Cli.Abstractions/Configuration/IJobConfiguration.cs @@ -0,0 +1,10 @@ +namespace Nox.Cli.Abstractions.Configuration; + +public interface IJobConfiguration +{ + string Id { get; set; } + string Name { get; set; } + string? If { get; set; } + NoxJobDisplayMessage? Display { get; set; } + List Steps { get; set; } +} \ No newline at end of file diff --git a/src/Nox.Cli.Abstractions/Configuration/IStepConfiguration.cs b/src/Nox.Cli.Abstractions/Configuration/IStepConfiguration.cs deleted file mode 100755 index b8550ea0..00000000 --- a/src/Nox.Cli.Abstractions/Configuration/IStepConfiguration.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Nox.Cli.Abstractions.Configuration; - -public interface IStepConfiguration -{ - List Steps { get; set; } -} \ No newline at end of file diff --git a/src/Nox.Cli.Abstractions/Configuration/IWorkflowConfiguration.cs b/src/Nox.Cli.Abstractions/Configuration/IWorkflowConfiguration.cs index 73ed27eb..f8643757 100755 --- a/src/Nox.Cli.Abstractions/Configuration/IWorkflowConfiguration.cs +++ b/src/Nox.Cli.Abstractions/Configuration/IWorkflowConfiguration.cs @@ -5,5 +5,5 @@ public interface IWorkflowConfiguration string Name { get; set; } string Description { get; set; } ICliConfiguration Cli { get; set; } - Dictionary Jobs { get; set; } + List Jobs { get; set; } } \ No newline at end of file diff --git a/src/Nox.Cli.Abstractions/INoxAction.cs b/src/Nox.Cli.Abstractions/INoxAction.cs index bea8378f..db445e25 100755 --- a/src/Nox.Cli.Abstractions/INoxAction.cs +++ b/src/Nox.Cli.Abstractions/INoxAction.cs @@ -5,7 +5,7 @@ public interface INoxAction int Sequence { get; set; } string Id { get; set; } string? If { get; set; } - string Job { get; set; } + string JobId { get; set; } string Name { get; set; } string Uses { get; set; } diff --git a/src/Nox.Cli.Abstractions/INoxJob.cs b/src/Nox.Cli.Abstractions/INoxJob.cs new file mode 100644 index 00000000..eb261ea0 --- /dev/null +++ b/src/Nox.Cli.Abstractions/INoxJob.cs @@ -0,0 +1,13 @@ +namespace Nox.Cli.Abstractions; + +public interface INoxJob +{ + int Sequence { get; set; } + int FirstStepSequence { get; set; } + string Id { get; set; } + + string Name { get; set; } + string? If { get; set; } + NoxJobDisplayMessage? Display { get; set; } + IDictionary Steps { get; set; } +} \ No newline at end of file diff --git a/src/Nox.Cli.Abstractions/INoxWorkflow.cs b/src/Nox.Cli.Abstractions/INoxWorkflow.cs new file mode 100644 index 00000000..438efbe1 --- /dev/null +++ b/src/Nox.Cli.Abstractions/INoxWorkflow.cs @@ -0,0 +1,6 @@ +namespace Nox.Cli.Abstractions; + +public interface INoxWorkflow +{ + public IDictionary Jobs { get; set; } +} \ No newline at end of file diff --git a/src/Nox.Cli.Abstractions/INoxWorkflowContext.cs b/src/Nox.Cli.Abstractions/INoxWorkflowContext.cs index 262caa10..eaa2f57f 100755 --- a/src/Nox.Cli.Abstractions/INoxWorkflowContext.cs +++ b/src/Nox.Cli.Abstractions/INoxWorkflowContext.cs @@ -1,6 +1,7 @@ using Nox.Cli.Abstractions.Caching; using Nox.Secrets.Abstractions; using Nox.Solution; +using Spectre.Console; namespace Nox.Cli.Abstractions { diff --git a/src/Nox.Cli.Abstractions/Nox.Cli.Abstractions.csproj b/src/Nox.Cli.Abstractions/Nox.Cli.Abstractions.csproj index 75209238..a2ecd47e 100755 --- a/src/Nox.Cli.Abstractions/Nox.Cli.Abstractions.csproj +++ b/src/Nox.Cli.Abstractions/Nox.Cli.Abstractions.csproj @@ -11,4 +11,10 @@ + + + + ..\Nox.Cli\bin\Debug\net7.0\Spectre.Console.dll + + diff --git a/src/Nox.Cli.Abstractions/NoxJobDisplayMessage.cs b/src/Nox.Cli.Abstractions/NoxJobDisplayMessage.cs new file mode 100644 index 00000000..a59a77aa --- /dev/null +++ b/src/Nox.Cli.Abstractions/NoxJobDisplayMessage.cs @@ -0,0 +1,7 @@ +namespace Nox.Cli.Abstractions; + +public class NoxJobDisplayMessage +{ + public string Success { get; set; } = string.Empty; + public string IfCondition { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/src/Nox.Cli.Caching/NoxCliCacheManager.cs b/src/Nox.Cli.Caching/NoxCliCacheManager.cs index 6c28c5fe..74cb9f34 100755 --- a/src/Nox.Cli.Caching/NoxCliCacheManager.cs +++ b/src/Nox.Cli.Caching/NoxCliCacheManager.cs @@ -395,7 +395,7 @@ private IDeserializer BuildDeserializer() .IgnoreUnmatchedProperties() .WithTypeMapping() .WithTypeMapping() - .WithTypeMapping() + .WithTypeMapping() .WithTypeMapping() .WithTypeMapping() .WithTypeMapping() diff --git a/src/Nox.Cli.Configuration/JobConfiguration.cs b/src/Nox.Cli.Configuration/JobConfiguration.cs new file mode 100644 index 00000000..9b6b81ec --- /dev/null +++ b/src/Nox.Cli.Configuration/JobConfiguration.cs @@ -0,0 +1,16 @@ +using Nox.Cli.Abstractions; +using Nox.Cli.Abstractions.Configuration; + +namespace Nox.Cli.Configuration; + +public class JobConfiguration: IJobConfiguration +{ + public string Id { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; + + public string? If { get; set; } + + public NoxJobDisplayMessage? Display { get; set; } + public List Steps { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Nox.Cli.Configuration/StepConfiguration.cs b/src/Nox.Cli.Configuration/StepConfiguration.cs deleted file mode 100755 index 0632dcc6..00000000 --- a/src/Nox.Cli.Configuration/StepConfiguration.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Nox.Cli.Abstractions; -using Nox.Cli.Abstractions.Configuration; - -namespace Nox.Cli.Configuration; - -public class StepConfiguration : IStepConfiguration -{ - public List Steps { get; set; } = new(); -} - diff --git a/src/Nox.Cli.Configuration/WorkflowConfiguration.cs b/src/Nox.Cli.Configuration/WorkflowConfiguration.cs index ebe95698..dd09e2f9 100755 --- a/src/Nox.Cli.Configuration/WorkflowConfiguration.cs +++ b/src/Nox.Cli.Configuration/WorkflowConfiguration.cs @@ -7,6 +7,6 @@ public class WorkflowConfiguration: IWorkflowConfiguration public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public ICliConfiguration Cli { get; set; } = null!; - public Dictionary Jobs { get; set; } = new(); + public List Jobs { get; set; } = new(); } diff --git a/src/Nox.Cli.Helpers/ConsoleWriter.cs b/src/Nox.Cli.Helpers/ConsoleWriter.cs index 84f9d354..d218a65d 100755 --- a/src/Nox.Cli.Helpers/ConsoleWriter.cs +++ b/src/Nox.Cli.Helpers/ConsoleWriter.cs @@ -4,13 +4,8 @@ public interface IConsoleWriter { - void WriteInfo(string message); - void WriteError(string message); - void WriteWarning(string message); - void WriteHelpHeader(string message); - void WriteHelpText(string message); - void WriteLogMessage(string message); - void WriteRuler(string message); + void WriteInfo(string key, string message); + void WriteHelpText(string key, string message); } public class ConsoleWriter : IConsoleWriter @@ -22,39 +17,14 @@ public ConsoleWriter(IAnsiConsole console) _console = console; } - public void WriteInfo(string message) + public void WriteInfo(string key, string message) { - _console.MarkupLine($"[bold mediumpurple3_1]{message.EscapeMarkup()}[/]"); + _console.MarkupLine($"[bold mediumpurple3_1]{key}: [/]{message.EscapeMarkup()}"); } - public void WriteError(string message) + public void WriteHelpText(string key, string message) { - _console.MarkupLine($"[bold indianred1]ERROR: {message.EscapeMarkup()}[/]"); - } - - public void WriteWarning(string message) - { - _console.MarkupLine($"[bold olive]WARNING: {message.EscapeMarkup()}[/]"); - } - - public void WriteHelpHeader(string message) - { - _console.MarkupLine($"[bold olive]{message.EscapeMarkup()}[/]"); - } - - public void WriteHelpText(string message) - { - _console.MarkupLine($"[bold seagreen1]{message.EscapeMarkup()}[/]"); - } - - public void WriteLogMessage(string message) - { - _console.MarkupLine($"[grey]{message}.[/]"); - } - - public void WriteRuler(string message) - { - _console.Write(new Rule($"[bold mediumpurple3_1]{message.EscapeMarkup()}[/]") { Justification = Justify.Left }); + _console.MarkupLine($"[bold seagreen1]{key}: [/]{message.EscapeMarkup()}"); } } \ No newline at end of file diff --git a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/ConsolePromptSchema_v1.cs b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/ConsolePromptSchema_v1.cs index f7bc6953..07f79d2e 100755 --- a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/ConsolePromptSchema_v1.cs +++ b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/ConsolePromptSchema_v1.cs @@ -4,7 +4,6 @@ using Nox.Cli.Abstractions; using Nox.Cli.Abstractions.Exceptions; using Nox.Cli.Abstractions.Extensions; -using Nox.Cli.Abstractions.Helpers; using Nox.Cli.Helpers; using Nox.Cli.Plugin.Console.JsonSchema; using RestSharp; @@ -217,8 +216,8 @@ public async Task> ProcessAsync(INoxWorkflowContext await File.WriteAllTextAsync(outputFilePath, _yaml.ToString()); outputs["file-path"] = outputFilePath; - AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine($"[bold mediumpurple3_1]Created {_fileOptions["filename"].EscapeMarkup()}[/]"); + //AnsiConsole.WriteLine(); + //AnsiConsole.MarkupLine($"[bold mediumpurple3_1]Created {_fileOptions["filename"].EscapeMarkup()}[/]"); } ctx.SetState(ActionState.Success); diff --git a/src/Nox.Cli.Shared.DTO/Task/Request/ServerAction.cs b/src/Nox.Cli.Shared.DTO/Task/Request/ServerAction.cs index d2067ba8..d1e642a2 100755 --- a/src/Nox.Cli.Shared.DTO/Task/Request/ServerAction.cs +++ b/src/Nox.Cli.Shared.DTO/Task/Request/ServerAction.cs @@ -7,7 +7,7 @@ public class ServerAction: INoxAction public int Sequence { get; set; } public string Id { get; set; } = string.Empty; public string? If { get; set; } - public string Job { get; set; } = string.Empty; + public string JobId { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string Uses { get; set; } = string.Empty; public bool? RunAtServer { get; set; } diff --git a/src/Nox.Cli/Actions/NoxAction.cs b/src/Nox.Cli/Actions/NoxAction.cs index bf3d0ed9..db53b2b6 100755 --- a/src/Nox.Cli/Actions/NoxAction.cs +++ b/src/Nox.Cli/Actions/NoxAction.cs @@ -8,7 +8,7 @@ public class NoxAction: INoxAction public int Sequence { get; set; } = 0; public string Id { get; set; } = string.Empty; public string? If { get; set; } = string.Empty; - public string Job { get; set; } = string.Empty; + public string JobId { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string Uses { get; set; } = string.Empty; public bool? RunAtServer { get; set; } = false; diff --git a/src/Nox.Cli/Actions/NoxJob.cs b/src/Nox.Cli/Actions/NoxJob.cs new file mode 100644 index 00000000..531d7351 --- /dev/null +++ b/src/Nox.Cli/Actions/NoxJob.cs @@ -0,0 +1,16 @@ +using Nox.Cli.Abstractions; + +namespace Nox.Cli.Actions; + +public class NoxJob: INoxJob +{ + public int Sequence { get; set; } + public int FirstStepSequence { get; set; } = 0; + public string Id { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; + public string? If { get; set; } + + public NoxJobDisplayMessage? Display { get; set; } = new(); + public IDictionary Steps { get; set; } = new Dictionary(); +} \ No newline at end of file diff --git a/src/Nox.Cli/Actions/NoxWorkflowContext.cs b/src/Nox.Cli/Actions/NoxWorkflowContext.cs index f0b1a61f..ede5242e 100755 --- a/src/Nox.Cli/Actions/NoxWorkflowContext.cs +++ b/src/Nox.Cli/Actions/NoxWorkflowContext.cs @@ -15,19 +15,24 @@ namespace Nox.Cli.Actions; public class NoxWorkflowContext : INoxWorkflowContext { private readonly IWorkflowConfiguration _workflow; - private readonly IDictionary _steps; + private readonly IDictionary _jobs; + private IDictionary _steps; private readonly IClientVariableProvider _varProvider; private readonly INoxCliCacheManager _cacheManager; private readonly INoxSecretsResolver? _secretsResolver; + + private int _currentJobSequence = 0; + private INoxJob? _currentJob; + private INoxJob? _nextJob; private int _currentActionSequence = 0; - - private INoxAction? _previousAction; private INoxAction? _currentAction; private INoxAction? _nextAction; private readonly Regex _secretsVariableRegex = new(@"\$\{\{\s*(?[\w\.\-_:]+secret[\w\.\-_:]+)\s*\}\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); - + + public INoxJob? CurrentJob => _currentJob; + public INoxAction? CurrentAction => _currentAction; public NoxWorkflowContext( @@ -43,17 +48,31 @@ public NoxWorkflowContext( _varProvider = new ClientVariableProvider(workflow, orgSecretResolver, projectConfig, lteConfig, cacheManager.Cache); _cacheManager = cacheManager; _secretsResolver = secretsResolver; - _steps = ParseSteps(); + _jobs = ParseWorkflow(); _currentActionSequence = 0; - NextStep(); + _steps = new Dictionary(); + NextJob(); } + public void NextJob() + { + _currentJobSequence++; + _currentJob = _jobs.Select(j => j.Value).FirstOrDefault(j => j.Sequence == _currentJobSequence); + _nextJob = _jobs.Select(j => j.Value).FirstOrDefault(j => j.Sequence == _currentJobSequence + 1); + + if (_currentJob != null) + { + _currentActionSequence = _currentJob.FirstStepSequence; + _steps = _currentJob.Steps; + NextStep(); + } + } + public void NextStep() { + _currentAction = _steps.Select(kv => kv.Value).FirstOrDefault(a => a.Sequence == _currentActionSequence); + _nextAction = _steps.Select(kv => kv.Value).FirstOrDefault(a => a.Sequence == _currentActionSequence + 1); _currentActionSequence++; - _previousAction = _currentAction; - _currentAction = _steps.Select(kv => kv.Value).Where(a => a.Sequence == _currentActionSequence).FirstOrDefault(); - _nextAction = _steps.Select(kv => kv.Value).Where(a => a.Sequence == _currentActionSequence + 1).FirstOrDefault(); } public bool IsServer => false; @@ -135,80 +154,124 @@ public IDictionary GetUnresolvedInputVariables(INoxAction action { return _varProvider.GetUnresolvedInputVariables(action); } - - private Dictionary ParseSteps() + + private Dictionary ParseWorkflow() { - var steps = new Dictionary(StringComparer.OrdinalIgnoreCase); - var sequence = 0; - foreach (var (jobKey, stepConfiguration) in _workflow.Jobs) + var jobSequence = 1; + var stepSequence = 1; + var jobs = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var jobConfiguration in _workflow.Jobs) { - foreach (var step in stepConfiguration.Steps) + var jobKey = jobConfiguration.Id; + + if (jobs.ContainsKey(jobConfiguration.Id)) + { + throw new NoxCliException($"Job Id {jobKey} exists more than once in your workflow configuration. Job Ids must be unique in a workflow configuration"); + } + + var newJob = new NoxJob + { + Sequence = jobSequence, + Id = jobConfiguration.Id, + Name = jobConfiguration.Name, + If = jobConfiguration.If, + Display = jobConfiguration.Display, + FirstStepSequence = stepSequence, + Steps = ParseSteps(jobConfiguration, ref stepSequence) + }; + + jobSequence++; + + if (newJob.Display != null) { - if (steps.ContainsKey(step.Id)) + if (!string.IsNullOrEmpty(newJob.Display.Success)) { - throw new NoxCliException($"Step Id {step.Id} exists more than once in your workflow configuration. Step Ids must be unique in a workflow configuration"); + newJob.Display.Success = MaskSecretsInDisplayText(newJob.Display.Success); } - - sequence++; - if (string.IsNullOrWhiteSpace(step.Uses)) + if (!string.IsNullOrEmpty(newJob.Display.IfCondition)) { - throw new Exception($"Step {sequence} ({step.Name}) is missing a 'uses' property"); + newJob.Display.IfCondition = MaskSecretsInDisplayText(newJob.Display.IfCondition); } + } - var actionType = NoxWorkflowContextHelpers.ResolveActionProviderTypeFromUses(step.Uses); + jobs[jobKey] = newJob; + } - if (actionType == null) - { - throw new Exception($"Step {sequence} ({step.Name}) uses {step.Uses} which was not found"); - } + return jobs; + } + + private Dictionary ParseSteps(IJobConfiguration jobConfiguration, ref int sequence) + { + var steps = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var step in jobConfiguration.Steps) + { + if (steps.ContainsKey(step.Id)) + { + throw new NoxCliException($"Step {step.Name} in job: {jobConfiguration.Name} exists more than once. Step Ids must be unique in a job configuration"); + } + + if (string.IsNullOrWhiteSpace(step.Uses)) + { + throw new Exception($"Step: {step.Name} in job: {jobConfiguration.Name} is missing a 'uses' property"); + } + + var actionType = NoxWorkflowContextHelpers.ResolveActionProviderTypeFromUses(step.Uses); + + if (actionType == null) + { + throw new Exception($"Step: {step.Name} in job: {jobConfiguration.Name} uses action: {step.Uses} which was not found"); + } - var newAction = new NoxAction() + var newAction = new NoxAction() + { + Sequence = sequence, + Id = step.Id, + JobId = jobConfiguration.Id, + Name = step.Name, + Uses = step.Uses, + If = step.If, + Validate = step.Validate, + Display = step.Display, + RunAtServer = step.RunAtServer, + ContinueOnError = step.ContinueOnError, + }; + newAction.ActionProvider = (INoxCliAddin)Activator.CreateInstance(actionType)!; + + sequence++; + + foreach (var (withKey, withValue) in step.With) + { + var input = new NoxActionInput { - Sequence = sequence, - Id = step.Id, - Job = jobKey, - Name = step.Name, - Uses = step.Uses, - If = step.If, - Validate = step.Validate, - Display = step.Display, - RunAtServer = step.RunAtServer, - ContinueOnError = step.ContinueOnError, + Id = withKey, + Default = withValue }; - newAction.ActionProvider = (INoxCliAddin)Activator.CreateInstance(actionType)!; - foreach (var (withKey, withValue) in step.With) - { - var input = new NoxActionInput - { - Id = withKey, - Default = withValue - }; + newAction.Inputs.Add(withKey, input); + } - newAction.Inputs.Add(withKey, input); - } - - if (newAction.Display != null) + if (newAction.Display != null) + { + if (!string.IsNullOrEmpty(newAction.Display.Error)) { - if (newAction.Display.Error != null) - { - newAction.Display.Error = MaskSecretsInDisplayText(newAction.Display.Error); - } - - if (newAction.Display.Success != null) - { - newAction.Display.Success = MaskSecretsInDisplayText(newAction.Display.Success); - } - if (newAction.Display.IfCondition != null) - { - newAction.Display.IfCondition = MaskSecretsInDisplayText(newAction.Display.IfCondition); - } + newAction.Display.Error = MaskSecretsInDisplayText(newAction.Display.Error); } - steps[newAction.Id] = newAction; + if (!string.IsNullOrEmpty(newAction.Display.Success)) + { + newAction.Display.Success = MaskSecretsInDisplayText(newAction.Display.Success); + } + if (!string.IsNullOrEmpty(newAction.Display.IfCondition)) + { + newAction.Display.IfCondition = MaskSecretsInDisplayText(newAction.Display.IfCondition); + } } + + steps[newAction.Id] = newAction; } return steps; diff --git a/src/Nox.Cli/Actions/NoxWorkflowExecutor.cs b/src/Nox.Cli/Actions/NoxWorkflowExecutor.cs index 4bda4a45..2b5391f9 100755 --- a/src/Nox.Cli/Actions/NoxWorkflowExecutor.cs +++ b/src/Nox.Cli/Actions/NoxWorkflowExecutor.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Configuration; -using Nox.Cli.Abstractions; +using Nox.Cli.Abstractions; using Nox.Cli.Abstractions.Caching; using Nox.Cli.Abstractions.Configuration; using Nox.Cli.Secrets; @@ -38,7 +37,7 @@ public NoxWorkflowExecutor( _cacheManager = cacheManager; _noxSecretsResolver = noxSecretsResolver; } - + public async Task Execute(IWorkflowConfiguration workflow) { var workflowDescription = $"[seagreen1]Executing workflow: {workflow.Name.EscapeMarkup()}[/]"; @@ -47,56 +46,73 @@ public async Task Execute(IWorkflowConfiguration workflow) var watch = System.Diagnostics.Stopwatch.StartNew(); - var ctx = _console.Status() + var success = true; + + var workflowCtx = _console.Status() .Spinner(Spinner.Known.Clock) .Start("Verifying the workflow script...", _ => new NoxWorkflowContext(workflow, _noxConfig, _orgSecretResolver, _cacheManager, _lteConfig, _noxSecretsResolver)); - bool success = true; - - while (ctx.CurrentAction != null) + while (workflowCtx.CurrentJob != null) { - if (ctx.CancellationToken != null) + if (workflowCtx.CancellationToken != null) { - _console.MarkupLine($"[yellow3]Workflow cancelled due to: {ctx.CancellationToken.Reason}[/]"); + _console.MarkupLine($"[yellow3]Workflow cancelled due to: {workflowCtx.CancellationToken.Reason}[/]"); break; } - - var taskDescription = $"Step {ctx.CurrentAction.Sequence}: {ctx.CurrentAction.Name}".EscapeMarkup(); - var formattedTaskDescription = $"[bold mediumpurple3_1]{taskDescription}[/]"; + _console.WriteLine(); + ConsoleRootLine($"[mediumpurple3_1]{workflowCtx.CurrentJob.Name.EscapeMarkup()}: [/]"); - var requiresConsole = ctx.CurrentAction.ActionProvider.Discover().RequiresConsole; - - if (ctx.CurrentAction.RunAtServer == true) + while (workflowCtx.CurrentAction != null) { - _console.WriteLine(); - _console.MarkupLine(formattedTaskDescription); - _console.MarkupLine($" {Emoji.Known.DesktopComputer} [bold yellow]Running at: {_serverIntegration!.Endpoint}[/]"); - success = await _console.Status().Spinner(Spinner.Known.Clock) - .StartAsync(formattedTaskDescription, async _ => - await ProcessServerTask(_console, ctx, formattedTaskDescription) - ); - } - else - { - if (requiresConsole) + if (workflowCtx.CancellationToken != null) { - _console.WriteLine(); - _console.MarkupLine(formattedTaskDescription); - success = await ProcessTask(_console, ctx); + break; } - else // show spinner + + var taskDescription = $"{workflowCtx.CurrentAction.Name}".EscapeMarkup(); + + if (workflowCtx.CurrentAction.RunAtServer == true) { - success = await _console.Status().Spinner(Spinner.Known.Clock) - .StartAsync(formattedTaskDescription, async _ => - await ProcessTask(_console, ctx, formattedTaskDescription) - ); + success = await _console + .Status() + .Spinner(Spinner.Known.Clock) + .StartAsync(taskDescription, async _ => await ProcessServerTask(workflowCtx, taskDescription)); } + else + { + var requiresConsole = workflowCtx.CurrentAction.ActionProvider.Discover().RequiresConsole; + if (requiresConsole) + { + success = await ProcessTask(workflowCtx, taskDescription); + } + else + { + + success = await _console + .Status() + .Spinner(Spinner.Known.Clock) + .StartAsync(taskDescription, async _ => await ProcessTask(workflowCtx, taskDescription)); + } + + } + + if (!success) break; + + workflowCtx.NextStep(); } - if (!success) break; + if (!success) + { + break; + } + + if (workflowCtx.CurrentJob.Display != null && !string.IsNullOrWhiteSpace(workflowCtx.CurrentJob.Display.Success)) + { + ConsoleRootLine($"{Emoji.Known.GreenSquare} [lightgreen]{workflowCtx.CurrentJob.Display.Success}[/]"); + } - ctx.NextStep(); + workflowCtx.NextJob(); } await Task.WhenAll(_processedActions.Where(p => p.RunAtServer == false).Select(p => p.ActionProvider.EndAsync())); @@ -118,10 +134,7 @@ await ProcessTask(_console, ctx, formattedTaskDescription) return success; } - private async Task ProcessTask( - IAnsiConsole console, - NoxWorkflowContext ctx, - string? formattedTaskDescription = null) + private async Task ProcessTask(NoxWorkflowContext ctx, string taskDescription) { if (ctx.CurrentAction == null) return false; @@ -129,19 +142,22 @@ private async Task ProcessTask( if (!ctx.CurrentAction.EvaluateIf()) { - if (!string.IsNullOrWhiteSpace(formattedTaskDescription)) + var skipMessage = ""; + if (!string.IsNullOrWhiteSpace(taskDescription)) { - console.WriteLine(); - console.MarkupLine(formattedTaskDescription); + skipMessage += $"{taskDescription}..."; } + if (string.IsNullOrWhiteSpace(ctx.CurrentAction.Display?.IfCondition)) { - console.MarkupLine($"{Emoji.Known.BlueCircle} Skipped because, if condition proved true"); + skipMessage += "Skipped because, if condition evaluated true"; } else { - console.MarkupLine($"{Emoji.Known.BlueCircle} {ctx.CurrentAction.Display.IfCondition.EscapeMarkup()}"); + skipMessage += ctx.CurrentAction.Display.IfCondition.EscapeMarkup(); } + + ConsoleStatusLine($"{Emoji.Known.BlueCircle} [deepskyblue1]{skipMessage}[/]"); return true; } @@ -149,11 +165,11 @@ private async Task ProcessTask( if (unresolved.Any()) { - console.WriteLine(); - console.MarkupLine($"{Emoji.Known.RedCircle} Some variables are unresolved. Did you misspell or forget to define them? Is the service.nox.yaml available?"); + _console.WriteLine(); + ConsoleStatusLine($"{Emoji.Known.RedCircle} Some variables are unresolved. Did you misspell or forget to define them? Is the service.nox.yaml available?"); foreach (var kv in unresolved) { - console.MarkupLine($" [bold mediumpurple3_1]{kv.Key}[/]: [indianred1]{kv.Value}[/]"); + ConsoleStatusLine($"[bold mediumpurple3_1]{kv.Key}[/]: [indianred1]{kv.Value}[/]"); } return false; } @@ -168,22 +184,22 @@ private async Task ProcessTask( _processedActions.Add(ctx.CurrentAction); - if (!string.IsNullOrWhiteSpace(formattedTaskDescription)) + if (ctx.CurrentAction.ActionProvider.Discover().RequiresConsole) { - console.WriteLine(); - console.MarkupLine(formattedTaskDescription); + _console.WriteLine(); + ConsoleRootLine($"[mediumpurple3_1]{ctx.CurrentJob!.Name.EscapeMarkup()}: [/]"); } - + if (ctx.CurrentAction.State == ActionState.Error) { if (ctx.CurrentAction.ContinueOnError) { - console.MarkupLine($"{Emoji.Known.GreenCircle} {ctx.CurrentAction.Display?.Error.EscapeMarkup() ?? string.Empty}"); + ConsoleStatusLine($"{Emoji.Known.GreenCircle} {ctx.CurrentAction.Display?.Error.EscapeMarkup() ?? string.Empty}"); } else { ctx.SetErrorMessage(ctx.CurrentAction, ctx.CurrentAction.ErrorMessage); - console.MarkupLine($"{Emoji.Known.RedCircle} [indianred1]{ctx.CurrentAction.Display?.Error.EscapeMarkup() ?? string.Empty}[/]"); + ConsoleStatusLine($"{Emoji.Known.RedCircle} [indianred1]{ctx.CurrentAction.Display?.Error.EscapeMarkup() ?? string.Empty}[/]"); return false; } } @@ -191,45 +207,46 @@ private async Task ProcessTask( { if (!string.IsNullOrWhiteSpace(ctx.CurrentAction.Display?.Success)) { - console.MarkupLine($"{Emoji.Known.GreenCircle} {ctx.CurrentAction.Display.Success.EscapeMarkup()}"); + ConsoleStatusLine($"{Emoji.Known.GreenCircle} [seagreen1]{ctx.CurrentAction.Display.Success.EscapeMarkup()}[/]"); } } return true; } - private async Task ProcessServerTask( - IAnsiConsole console, - NoxWorkflowContext ctx, - string? formattedTaskDescription = null) + private async Task ProcessServerTask(NoxWorkflowContext ctx, string taskDescription) { if (ctx.CurrentAction == null) return false; if (!await IsServerHealthy()) { ctx.SetErrorMessage(ctx.CurrentAction, "Unable to connect to Nox Cli Server."); - console.MarkupLine($"{Emoji.Known.RedCircle} [indianred1]Unable to connect to Nox Cli Server, you are trying to execute an action on the Nox Cli Server, but the server endpoint is not currently available[/]"); + ConsoleStatusLine($"{Emoji.Known.RedCircle} [indianred1]Unable to connect to Nox Cli Server, you are trying to execute an action on the Nox Cli Server, but the server endpoint is not currently available[/]"); return false; } + ConsoleInfoLine($"{Emoji.Known.DesktopComputer} [bold yellow]Running at: {_serverIntegration!.Endpoint}[/]"); + await ctx.GetInputVariables(ctx.CurrentAction, true); if (!ctx.CurrentAction.EvaluateIf()) { - if (!string.IsNullOrWhiteSpace(formattedTaskDescription)) + var skipMessage = ""; + if (!string.IsNullOrWhiteSpace(taskDescription)) { - console.WriteLine(); - console.MarkupLine(formattedTaskDescription); + skipMessage += $"{taskDescription}..."; } - + if (string.IsNullOrWhiteSpace(ctx.CurrentAction.Display?.IfCondition)) { - console.MarkupLine($"{Emoji.Known.BlueCircle} Skipped because, if condition proved true"); + skipMessage += "Skipped because, if condition evaluated true"; } else { - console.MarkupLine($"{Emoji.Known.BlueCircle} {ctx.CurrentAction.Display.IfCondition.EscapeMarkup()}"); + skipMessage += ctx.CurrentAction.Display.IfCondition.EscapeMarkup(); } + + ConsoleStatusLine($"{Emoji.Known.BlueCircle} [deepskyblue1]{skipMessage}[/]"); return true; } @@ -247,12 +264,12 @@ private async Task ProcessServerTask( { if (ctx.CurrentAction.ContinueOnError) { - console.MarkupLine($"{Emoji.Known.GreenCircle} {ctx.CurrentAction.Display?.Error.EscapeMarkup() ?? string.Empty}"); + ConsoleStatusLine($"{Emoji.Known.GreenCircle} {ctx.CurrentAction.Display?.Error.EscapeMarkup() ?? string.Empty}"); } else { ctx.SetErrorMessage(ctx.CurrentAction, executeResponse.ErrorMessage!); - console.MarkupLine($"{Emoji.Known.RedCircle} [indianred1]{ctx.CurrentAction.Display?.Error.EscapeMarkup() ?? string.Empty}[/]"); + ConsoleStatusLine($"{Emoji.Known.RedCircle} [indianred1]{ctx.CurrentAction.Display?.Error.EscapeMarkup() ?? string.Empty}[/]"); return false; } } @@ -260,7 +277,7 @@ private async Task ProcessServerTask( { if (!string.IsNullOrWhiteSpace(ctx.CurrentAction.Display?.Success)) { - console.MarkupLine($"{Emoji.Known.GreenCircle} {ctx.CurrentAction.Display.Success.EscapeMarkup()}"); + ConsoleStatusLine($"{Emoji.Known.GreenCircle} [seagreen1]{ctx.CurrentAction.Display.Success.EscapeMarkup()}[/]"); } } @@ -274,6 +291,24 @@ private async Task IsServerHealthy() if (result == null) return false; return result.Name == "Nox Cli Server"; } + + private void ConsoleRootLine(string value) + { + _console.MarkupLine(value); + } + + private void ConsoleStatusLine(string value) + { + var padding = new string(' ', 4); + _console.MarkupLine($"{padding}{value}"); + } + + private void ConsoleInfoLine(string value) + { + var padding = new string(' ', 8); + _console.MarkupLine($"{padding}{value}"); + } + } diff --git a/src/Nox.Cli/Commands/Base/NoxCliCommand.cs b/src/Nox.Cli/Commands/Base/NoxCliCommand.cs index 08a6274a..fae76da8 100755 --- a/src/Nox.Cli/Commands/Base/NoxCliCommand.cs +++ b/src/Nox.Cli/Commands/Base/NoxCliCommand.cs @@ -23,8 +23,6 @@ public NoxCliCommand(IAnsiConsole console, IConsoleWriter consoleWriter, public override Task ExecuteAsync(CommandContext context, TSettings settings) { - _console.WriteLine(); - _consoleWriter.WriteInfo($"Design folder:"); if (string.IsNullOrEmpty(_solution.Name)) { @@ -38,11 +36,10 @@ public override Task ExecuteAsync(CommandContext context, TSettings setting } _console.WriteLine(); - _consoleWriter.WriteHelpText("Reading: Project definition..."); + _consoleWriter.WriteHelpText("Reading","Project definition..."); _console.WriteLine(); - _consoleWriter.WriteInfo($"Project:"); - _console.WriteLine(_solution.Name); + _consoleWriter.WriteInfo("Project", _solution.Name); return Task.FromResult(0); } diff --git a/src/Nox.Cli/Properties/launchSettings.json b/src/Nox.Cli/Properties/launchSettings.json index da3c438d..3afa2899 100755 --- a/src/Nox.Cli/Properties/launchSettings.json +++ b/src/Nox.Cli/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Nox.Cli": { "commandName": "Project", - "commandLineArgs": "init solution", + "commandLineArgs": "test workflow", "workingDirectory": "/home/jan/demo/CliDemo", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/tests/Nox.Cli.Server.Tests/TestHelper.cs b/tests/Nox.Cli.Server.Tests/TestHelper.cs index d2ccc124..143fa887 100755 --- a/tests/Nox.Cli.Server.Tests/TestHelper.cs +++ b/tests/Nox.Cli.Server.Tests/TestHelper.cs @@ -183,7 +183,7 @@ public static IWorkflowConfiguration GetWorkflowFromFile(string yaml) .IgnoreUnmatchedProperties() .WithTypeMapping() .WithTypeMapping() - .WithTypeMapping() + .WithTypeMapping() .WithTypeMapping() .WithTypeMapping() .WithTypeMapping()