Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/TALXIS.CLI.Core/Contracts/Dataverse/IPluginInventoryService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace TALXIS.CLI.Core.Contracts.Dataverse;

public enum PluginKind { Plugin = 0, WorkflowActivity = 1 }

public enum PluginStage
{
PreValidation = 10,
PreOperation = 20,
PostOperation = 40,
PostOperationDeprecated = 50,
}

public enum PluginExecutionMode { Synchronous = 0, Asynchronous = 1 }

public enum PluginIsolationMode { None = 1, Sandbox = 2, External = 3 }

public enum PluginAssemblySourceType { Database = 0, Disk = 1, Gac = 2, Package = 4 }

public sealed record PluginAssemblyRecord(
Guid Id,
string Name,
string? Version,
string? Culture,
string? PublicKeyToken,
PluginIsolationMode IsolationMode,
PluginAssemblySourceType SourceType,
string? Description,
DateTime? ModifiedOn);

public sealed record PluginTypeRecord(
Guid Id,
string TypeName,
string? FriendlyName,
PluginKind Kind,
string? WorkflowActivityGroupName,
string? Description,
Guid AssemblyId,
string AssemblyName,
string? AssemblyVersion);

public sealed record PluginStepRecord(
Guid Id,
string Name,
string? Description,
string Message,
string? PrimaryEntity,
PluginStage Stage,
PluginExecutionMode Mode,
int Rank,
bool Enabled,
string? FilteringAttributes,
string? Configuration,
Guid PluginTypeId,
string PluginTypeName,
Guid AssemblyId,
string AssemblyName,
string? AssemblyVersion);

public interface IPluginInventoryService
{
Task<IReadOnlyList<PluginAssemblyRecord>> ListAssembliesAsync(
string? profileName,
string? nameContains,
CancellationToken ct);

Task<IReadOnlyList<PluginTypeRecord>> ListTypesAsync(
string? profileName,
string? assemblyContains,
PluginKind? kind,
CancellationToken ct);

Task<IReadOnlyList<PluginStepRecord>> ListStepsAsync(
string? profileName,
string? assemblyContains,
CancellationToken ct);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace TALXIS.CLI.Features.Environment;
Name = "environment",
Alias = "env",
Description = "Manage the footprint of your project in a live target environment (packages, solutions, deployment history).",
Children = new[] { typeof(Package.PackageCliCommand), typeof(Solution.SolutionCliCommand), typeof(Deployment.DeploymentCliCommand), typeof(Data.EnvDataCliCommand), typeof(Entity.EntityCliCommand), typeof(OptionSet.OptionSetCliCommand), typeof(Setting.SettingCliCommand), typeof(Changeset.ChangesetCliCommand), typeof(Component.ComponentCliCommand), typeof(Publisher.PublisherCliCommand) },
Children = new[] { typeof(Package.PackageCliCommand), typeof(Solution.SolutionCliCommand), typeof(Deployment.DeploymentCliCommand), typeof(Data.EnvDataCliCommand), typeof(Entity.EntityCliCommand), typeof(OptionSet.OptionSetCliCommand), typeof(Setting.SettingCliCommand), typeof(Changeset.ChangesetCliCommand), typeof(Component.ComponentCliCommand), typeof(Publisher.PublisherCliCommand), typeof(Plugin.PluginCliCommand) },
ShortFormAutoGenerate = CliNameAutoGenerate.None
)]
public class EnvironmentCliCommand
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using DotMake.CommandLine;

namespace TALXIS.CLI.Features.Environment.Plugin.Assemblies;

[CliCommand(
Name = "assembly",
Description = "List plugin assemblies registered in the connected environment.",
Children = new[] { typeof(PluginAssemblyListCliCommand) },
ShortFormAutoGenerate = CliNameAutoGenerate.None
)]
public class PluginAssemblyCliCommand
{
public void Run(CliContext context)
{
context.ShowHelp();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using DotMake.CommandLine;
using Microsoft.Extensions.Logging;
using TALXIS.CLI.Core;
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;

namespace TALXIS.CLI.Features.Environment.Plugin.Assemblies;

[CliReadOnly]
[CliCommand(
Name = "list",
Description = "List plugin assemblies registered in the connected environment. Useful for verifying which version of an assembly is currently deployed."
)]
public class PluginAssemblyListCliCommand : ProfiledCliCommand
{
protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(PluginAssemblyListCliCommand));

[CliOption(Name = "--name", Description = "Filter to assemblies whose name contains this substring.", Required = false)]
public string? Name { get; set; }

protected override async Task<int> ExecuteAsync()
{
var service = TxcServices.Get<IPluginInventoryService>();
var rows = await service.ListAssembliesAsync(Profile, Name, CancellationToken.None).ConfigureAwait(false);

OutputFormatter.WriteList(rows, PrintTable);
return ExitSuccess;
}

#pragma warning disable TXC003
private static void PrintTable(IReadOnlyList<PluginAssemblyRecord> rows)
{
if (rows.Count == 0) { OutputWriter.WriteLine("No plugin assemblies found."); return; }

int nameWidth = Math.Clamp(rows.Max(r => r.Name.Length), 20, 60);
int versionWidth = Math.Clamp(rows.Max(r => (r.Version ?? "").Length), 7, 18);
string header = $"{"Name".PadRight(nameWidth)} | {"Version".PadRight(versionWidth)} | {"Isolation".PadRight(9)} | {"Source".PadRight(8)} | Modified";
OutputWriter.WriteLine(header);
OutputWriter.WriteLine(new string('-', header.Length));
foreach (var r in rows)
{
string name = r.Name.Length > nameWidth ? r.Name[..(nameWidth - 1)] + "." : r.Name;
string version = (r.Version ?? "").PadRight(versionWidth);
string modified = r.ModifiedOn?.ToString("yyyy-MM-dd HH:mm") ?? "";
OutputWriter.WriteLine($"{name.PadRight(nameWidth)} | {version} | {r.IsolationMode.ToString().PadRight(9)} | {r.SourceType.ToString().PadRight(8)} | {modified}");
}
}
#pragma warning restore TXC003
}
22 changes: 22 additions & 0 deletions src/TALXIS.CLI.Features.Environment/Plugin/PluginCliCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using DotMake.CommandLine;

namespace TALXIS.CLI.Features.Environment.Plugin;

[CliCommand(
Name = "plugin",
Description = "Inspect plugin assemblies, plugin types, and processing steps registered in the connected environment.",
Children = new[]
{
typeof(Assemblies.PluginAssemblyCliCommand),
typeof(Types.PluginTypeCliCommand),
typeof(Steps.PluginStepCliCommand),
},
ShortFormAutoGenerate = CliNameAutoGenerate.None
)]
public class PluginCliCommand
{
public void Run(CliContext context)
{
context.ShowHelp();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using DotMake.CommandLine;

namespace TALXIS.CLI.Features.Environment.Plugin.Steps;

[CliCommand(
Name = "step",
Description = "List plugin processing steps registered in the connected environment.",
Children = new[] { typeof(PluginStepListCliCommand) },
ShortFormAutoGenerate = CliNameAutoGenerate.None
)]
public class PluginStepCliCommand
{
public void Run(CliContext context)
{
context.ShowHelp();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using DotMake.CommandLine;
using Microsoft.Extensions.Logging;
using TALXIS.CLI.Core;
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;

namespace TALXIS.CLI.Features.Environment.Plugin.Steps;

[CliReadOnly]
[CliCommand(
Name = "list",
Description = "List plugin processing steps (SdkMessageProcessingStep) registered in the connected environment. Shows which message + entity each step fires on, its stage, mode, rank, and enabled state."
)]
public class PluginStepListCliCommand : ProfiledCliCommand
{
protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(PluginStepListCliCommand));

[CliOption(Name = "--assembly", Description = "Filter to steps whose owning assembly name contains this substring.", Required = false)]
public string? Assembly { get; set; }

protected override async Task<int> ExecuteAsync()
{
var service = TxcServices.Get<IPluginInventoryService>();
var rows = await service.ListStepsAsync(Profile, Assembly, CancellationToken.None).ConfigureAwait(false);

OutputFormatter.WriteList(rows, PrintTable);
return ExitSuccess;
}

#pragma warning disable TXC003
private static void PrintTable(IReadOnlyList<PluginStepRecord> rows)
{
if (rows.Count == 0) { OutputWriter.WriteLine("No plugin steps found."); return; }

var ordered = rows
.OrderBy(r => r.AssemblyName, StringComparer.OrdinalIgnoreCase)
.ThenBy(r => r.PluginTypeName, StringComparer.OrdinalIgnoreCase)
.ThenBy(r => r.Rank)
.ToList();

int msgWidth = Math.Clamp(ordered.Max(r => r.Message.Length), 8, 18);
int entityWidth = Math.Clamp(ordered.Max(r => (r.PrimaryEntity ?? "").Length), 8, 22);
int typeWidth = Math.Clamp(ordered.Max(r => r.PluginTypeName.Length), 30, 60);

string header = $"{"Message".PadRight(msgWidth)} | {"Entity".PadRight(entityWidth)} | {"Stage".PadRight(15)} | {"Mode".PadRight(5)} | {"Rank".PadRight(4)} | {"State".PadRight(8)} | {"Plugin Type".PadRight(typeWidth)} | Step Name";
OutputWriter.WriteLine(header);
OutputWriter.WriteLine(new string('-', header.Length));
foreach (var r in ordered)
{
string entity = (r.PrimaryEntity ?? "").PadRight(entityWidth);
string typeName = r.PluginTypeName.Length > typeWidth ? r.PluginTypeName[..(typeWidth - 1)] + "." : r.PluginTypeName;
string state = r.Enabled ? "Enabled" : "Disabled";
string mode = r.Mode == PluginExecutionMode.Synchronous ? "Sync" : "Async";
OutputWriter.WriteLine($"{r.Message.PadRight(msgWidth)} | {entity} | {r.Stage.ToString().PadRight(15)} | {mode.PadRight(5)} | {r.Rank.ToString().PadRight(4)} | {state.PadRight(8)} | {typeName.PadRight(typeWidth)} | {r.Name}");
}
}
#pragma warning restore TXC003
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using DotMake.CommandLine;

namespace TALXIS.CLI.Features.Environment.Plugin.Types;

[CliCommand(
Name = "type",
Description = "List plugin types (plugins and workflow activities) registered in the connected environment.",
Children = new[] { typeof(PluginTypeListCliCommand) },
ShortFormAutoGenerate = CliNameAutoGenerate.None
)]
public class PluginTypeCliCommand
{
public void Run(CliContext context)
{
context.ShowHelp();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using DotMake.CommandLine;
using Microsoft.Extensions.Logging;
using TALXIS.CLI.Core;
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;

namespace TALXIS.CLI.Features.Environment.Plugin.Types;

[CliReadOnly]
[CliCommand(
Name = "list",
Description = "List plugin types (plugins and workflow activities) registered in the connected environment."
)]
public class PluginTypeListCliCommand : ProfiledCliCommand
{
protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(PluginTypeListCliCommand));

[CliOption(Name = "--assembly", Description = "Filter to plugin types whose parent assembly name contains this substring.", Required = false)]
public string? Assembly { get; set; }

[CliOption(Name = "--kind", Description = "Filter by kind: plugin, workflow, or all (default).", Required = false)]
public string? Kind { get; set; }

protected override async Task<int> ExecuteAsync()
{
PluginKind? kind = null;
if (!string.IsNullOrWhiteSpace(Kind))
{
switch (Kind.Trim().ToLowerInvariant())
{
case "plugin": kind = PluginKind.Plugin; break;
case "workflow":
case "workflowactivity":
case "wf": kind = PluginKind.WorkflowActivity; break;
case "all": kind = null; break;
default:
Logger.LogError("Invalid --kind value '{Kind}'. Expected: plugin, workflow, or all.", Kind);
return ExitValidationError;
}
}

var service = TxcServices.Get<IPluginInventoryService>();
var rows = await service.ListTypesAsync(Profile, Assembly, kind, CancellationToken.None).ConfigureAwait(false);

OutputFormatter.WriteList(rows, PrintTable);
return ExitSuccess;
}

#pragma warning disable TXC003
private static void PrintTable(IReadOnlyList<PluginTypeRecord> rows)
{
if (rows.Count == 0) { OutputWriter.WriteLine("No plugin types found."); return; }

int typeWidth = Math.Clamp(rows.Max(r => r.TypeName.Length), 30, 70);
int assemblyWidth = Math.Clamp(rows.Max(r => r.AssemblyName.Length), 15, 45);
int versionWidth = Math.Clamp(rows.Max(r => (r.AssemblyVersion ?? "").Length), 7, 15);
string header = $"{"Type Name".PadRight(typeWidth)} | {"Kind".PadRight(16)} | {"Assembly".PadRight(assemblyWidth)} | {"Version".PadRight(versionWidth)} | WF Group";
OutputWriter.WriteLine(header);
OutputWriter.WriteLine(new string('-', header.Length));
foreach (var r in rows)
{
string typeName = r.TypeName.Length > typeWidth ? r.TypeName[..(typeWidth - 1)] + "." : r.TypeName;
string asm = r.AssemblyName.Length > assemblyWidth ? r.AssemblyName[..(assemblyWidth - 1)] + "." : r.AssemblyName;
string ver = (r.AssemblyVersion ?? "").PadRight(versionWidth);
string group = r.WorkflowActivityGroupName ?? "";
OutputWriter.WriteLine($"{typeName.PadRight(typeWidth)} | {r.Kind.ToString().PadRight(16)} | {asm.PadRight(assemblyWidth)} | {ver} | {group}");
}
}
#pragma warning restore TXC003
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static IServiceCollection AddTxcDataverseApplication(this IServiceCollect
services.AddSingleton<ISolutionPublishService, DataverseSolutionPublishService>();
services.AddSingleton<ISolutionComponentMutationService, DataverseSolutionComponentMutationService>();
services.AddSingleton<IPublisherService, DataversePublisherService>();
services.AddSingleton<IPluginInventoryService, DataversePluginInventoryService>();
services.AddSingleton<IMetadataIdResolver, DataverseMetadataIdResolver>();
services.AddSingleton<ISolutionLayerMutationService, DataverseSolutionLayerMutationService>();
services.AddSingleton<ISolutionPackagerService, Sdk.SolutionPackagerServiceImpl>();
Expand Down
Loading