Skip to content

Commit 11d910b

Browse files
authored
Merge pull request #1 from TALXIS/users/tomas.prokop/components
Improved MCP implementation and added templates
2 parents 384b630 + cb0e60b commit 11d910b

23 files changed

+826
-133
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,22 @@ Converts tables from an Excel `.xlsx` file into a structured CMT format. Each ta
4949
txc data convert --input <export.xlsx> --output <data.xml>
5050
```
5151

52-
#### `server` command
52+
#### `transform-server` command
5353

5454
Starts a simple local HTTP server exposing endpoints for ETL/data transformation tasks. Useful for integrating with Power Query or other local ETL tools.
5555

5656
**Usage:**
5757

5858
```sh
59-
txc data server [--port <port>]
59+
txc data transform-server [--port <port>]
6060
```
6161

6262
- `--port` (optional): Port to run the server on. Defaults to `50505` if not specified.
6363

6464
**Example:**
6565

6666
```sh
67-
txc data server --port 50505
67+
txc data transform-server --port 50505
6868
```
6969

7070

@@ -135,9 +135,9 @@ To build and debug the CLI locally:
135135
```sh
136136
dotnet build
137137
```
138-
3. Run the CLI directly (for example, to test the data server):
138+
3. Run the CLI directly (for example, to test the data transform server):
139139
```sh
140-
dotnet run --project src/TALXIS.CLI -- data server
140+
dotnet run --project src/TALXIS.CLI -- data transform-server
141141
```
142142
4. You can also debug using Visual Studio or VS Code by opening the solution and setting breakpoints as needed.
143143

TALXIS.CLI.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TALXIS.CLI.Data", "src\TALXIS.CLI.Data\TALXIS.CLI.Data.csproj", "{5661B9D5-76FD-DAFA-278B-5B9BE78D957D}"
99
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TALXIS.CLI.Component", "src\TALXIS.CLI.Component\TALXIS.CLI.Component.csproj", "{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}"
11+
EndProject
1012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TALXIS.CLI", "src\TALXIS.CLI\TALXIS.CLI.csproj", "{047E218E-A6A2-1C66-58E1-AFEF0AD34E7F}"
1113
EndProject
1214
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TALXIS.CLI.MCP", "src\TALXIS.CLI.MCP\TALXIS.CLI.MCP.csproj", "{DFE5EC2E-21E2-42D6-B9C6-3111CE00FD0B}"
@@ -33,6 +35,18 @@ Global
3335
{5661B9D5-76FD-DAFA-278B-5B9BE78D957D}.Release|x64.Build.0 = Release|Any CPU
3436
{5661B9D5-76FD-DAFA-278B-5B9BE78D957D}.Release|x86.ActiveCfg = Release|Any CPU
3537
{5661B9D5-76FD-DAFA-278B-5B9BE78D957D}.Release|x86.Build.0 = Release|Any CPU
38+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|x64.ActiveCfg = Debug|Any CPU
41+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|x64.Build.0 = Debug|Any CPU
42+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|x86.ActiveCfg = Debug|Any CPU
43+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|x86.Build.0 = Debug|Any CPU
44+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
45+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|Any CPU.Build.0 = Release|Any CPU
46+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|x64.ActiveCfg = Release|Any CPU
47+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|x64.Build.0 = Release|Any CPU
48+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|x86.ActiveCfg = Release|Any CPU
49+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|x86.Build.0 = Release|Any CPU
3650
{047E218E-A6A2-1C66-58E1-AFEF0AD34E7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3751
{047E218E-A6A2-1C66-58E1-AFEF0AD34E7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
3852
{047E218E-A6A2-1C66-58E1-AFEF0AD34E7F}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -63,6 +77,7 @@ Global
6377
EndGlobalSection
6478
GlobalSection(NestedProjects) = preSolution
6579
{5661B9D5-76FD-DAFA-278B-5B9BE78D957D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
80+
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
6681
{047E218E-A6A2-1C66-58E1-AFEF0AD34E7F} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
6782
{DFE5EC2E-21E2-42D6-B9C6-3111CE00FD0B} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
6883
EndGlobalSection
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using DotMake.CommandLine;
2+
3+
namespace TALXIS.CLI.Component;
4+
5+
/// <summary>
6+
/// Base class for component commands that provides common functionality for create, delete, list, and explain operations.
7+
/// </summary>
8+
public abstract class BaseComponentCommand
9+
{
10+
/// <summary>
11+
/// Gets the name of the component type (e.g., "app", "entity", "form").
12+
/// </summary>
13+
protected abstract string ComponentType { get; }
14+
15+
/// <summary>
16+
/// Gets a description of what this component represents.
17+
/// </summary>
18+
protected abstract string ComponentDescription { get; }
19+
20+
/// <summary>
21+
/// Creates a new component instance.
22+
/// </summary>
23+
/// <param name="name">The name of the component to create.</param>
24+
/// <param name="options">Additional options for component creation.</param>
25+
public virtual async Task CreateAsync(string name, ComponentOptions? options = null)
26+
{
27+
Console.WriteLine($"Creating {ComponentType}: {name}");
28+
29+
if (options?.Verbose == true)
30+
{
31+
Console.WriteLine($"Component type: {ComponentType}");
32+
Console.WriteLine($"Description: {ComponentDescription}");
33+
}
34+
35+
await PerformCreateAsync(name, options);
36+
37+
Console.WriteLine($"✅ Successfully created {ComponentType}: {name}");
38+
}
39+
40+
/// <summary>
41+
/// Deletes an existing component instance.
42+
/// </summary>
43+
/// <param name="name">The name of the component to delete.</param>
44+
/// <param name="options">Additional options for component deletion.</param>
45+
public virtual async Task DeleteAsync(string name, ComponentOptions? options = null)
46+
{
47+
Console.WriteLine($"Deleting {ComponentType}: {name}");
48+
49+
if (options?.Force != true)
50+
{
51+
Console.Write($"Are you sure you want to delete {ComponentType} '{name}'? (y/N): ");
52+
var confirmation = Console.ReadLine();
53+
if (!string.Equals(confirmation?.Trim(), "y", StringComparison.OrdinalIgnoreCase))
54+
{
55+
Console.WriteLine("Operation cancelled.");
56+
return;
57+
}
58+
}
59+
60+
await PerformDeleteAsync(name, options);
61+
62+
Console.WriteLine($"✅ Successfully deleted {ComponentType}: {name}");
63+
}
64+
65+
/// <summary>
66+
/// Lists all existing component instances.
67+
/// </summary>
68+
/// <param name="options">Additional options for listing components.</param>
69+
public virtual async Task ListAsync(ComponentOptions? options = null)
70+
{
71+
Console.WriteLine($"Listing all {ComponentType} components:");
72+
73+
var components = await GetComponentListAsync(options);
74+
75+
if (!components.Any())
76+
{
77+
Console.WriteLine($"No {ComponentType} components found.");
78+
return;
79+
}
80+
81+
foreach (var component in components)
82+
{
83+
if (options?.Verbose == true)
84+
{
85+
Console.WriteLine($" 📦 {component.Name} - {component.Description}");
86+
if (!string.IsNullOrEmpty(component.Path))
87+
{
88+
Console.WriteLine($" 📁 Path: {component.Path}");
89+
}
90+
if (component.LastModified.HasValue)
91+
{
92+
Console.WriteLine($" 📅 Modified: {component.LastModified:yyyy-MM-dd HH:mm:ss}");
93+
}
94+
}
95+
else
96+
{
97+
Console.WriteLine($" 📦 {component.Name}");
98+
}
99+
}
100+
101+
Console.WriteLine($"\nTotal: {components.Count()} {ComponentType} component(s)");
102+
}
103+
104+
/// <summary>
105+
/// Explains what this component type is and how to use it.
106+
/// </summary>
107+
/// <param name="options">Additional options for explanation.</param>
108+
public virtual Task ExplainAsync(ComponentOptions? options = null)
109+
{
110+
Console.WriteLine($"📋 {ComponentType.ToUpperInvariant()} Component");
111+
Console.WriteLine($"Description: {ComponentDescription}");
112+
Console.WriteLine();
113+
Console.WriteLine("Available operations:");
114+
Console.WriteLine($" • create - Create a new {ComponentType} component");
115+
Console.WriteLine($" • delete - Delete an existing {ComponentType} component");
116+
Console.WriteLine($" • list - List all {ComponentType} components");
117+
Console.WriteLine($" • explain - Show this explanation");
118+
Console.WriteLine();
119+
120+
ProvideAdditionalExplanation();
121+
122+
return Task.CompletedTask;
123+
}
124+
125+
/// <summary>
126+
/// Performs the actual component creation logic. Override in derived classes.
127+
/// </summary>
128+
/// <param name="name">The name of the component to create.</param>
129+
/// <param name="options">Additional options for component creation.</param>
130+
protected abstract Task PerformCreateAsync(string name, ComponentOptions? options);
131+
132+
/// <summary>
133+
/// Performs the actual component deletion logic. Override in derived classes.
134+
/// </summary>
135+
/// <param name="name">The name of the component to delete.</param>
136+
/// <param name="options">Additional options for component deletion.</param>
137+
protected abstract Task PerformDeleteAsync(string name, ComponentOptions? options);
138+
139+
/// <summary>
140+
/// Gets the list of existing components. Override in derived classes.
141+
/// </summary>
142+
/// <param name="options">Additional options for listing components.</param>
143+
protected abstract Task<IEnumerable<ComponentInfo>> GetComponentListAsync(ComponentOptions? options);
144+
145+
/// <summary>
146+
/// Provides additional component-specific explanation. Override in derived classes if needed.
147+
/// </summary>
148+
protected virtual void ProvideAdditionalExplanation()
149+
{
150+
// Default implementation - can be overridden
151+
}
152+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using DotMake.CommandLine;
2+
namespace TALXIS.CLI.Component;
3+
4+
[CliCommand(
5+
Description = "Component scaffolding utilities for TALXIS solutions.",
6+
Children = new[] {
7+
typeof(AppComponentCommand),
8+
typeof(ListTemplatesCliCommand),
9+
typeof(ListTemplateParametersCliCommand),
10+
}
11+
)]
12+
public class ComponentCliCommand
13+
{
14+
public void Run(CliContext context)
15+
{
16+
context.ShowHelp();
17+
}
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace TALXIS.CLI.Component;
4+
5+
public class ComponentInfo
6+
{
7+
public required string Name { get; set; }
8+
public string? Description { get; set; }
9+
public string? Path { get; set; }
10+
public DateTime? LastModified { get; set; }
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
using System;
3+
4+
namespace TALXIS.CLI.Component;
5+
6+
public class ComponentOptions
7+
{
8+
public bool Verbose { get; set; }
9+
public bool Force { get; set; }
10+
public Dictionary<string, object> Parameters { get; set; } = new();
11+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.TemplateEngine.IDE;
8+
using Microsoft.TemplateEngine.Abstractions;
9+
using Microsoft.TemplateEngine.Abstractions.Installer;
10+
using Microsoft.TemplateEngine.Edge;
11+
using Microsoft.TemplateEngine.Orchestrator.RunnableProjects;
12+
using Microsoft.Extensions.Logging.Abstractions;
13+
14+
namespace TALXIS.CLI.Component
15+
{
16+
public class ComponentScaffolder : IDisposable
17+
{
18+
private readonly Bootstrapper _bootstrapper;
19+
private readonly string _templatePackageName = "TALXIS.DevKit.Templates.Dataverse";
20+
private bool _isTemplateInstalled = false;
21+
22+
public ComponentScaffolder()
23+
{
24+
var version = typeof(ComponentScaffolder).Assembly.GetName().Version?.ToString() ?? "1.0.0.0";
25+
var host = new DefaultTemplateEngineHost("TALXIS.CLI.Component", version);
26+
_bootstrapper = new Bootstrapper(
27+
host,
28+
loadDefaultComponents: true,
29+
virtualizeConfiguration: false,
30+
environment: null);
31+
}
32+
33+
public async Task EnsureTemplatePackageInstalled(string? version = null)
34+
{
35+
if (_isTemplateInstalled) return;
36+
var packageId = _templatePackageName + (version != null ? $"::{version}" : "");
37+
var installRequests = new[] { new Microsoft.TemplateEngine.Abstractions.Installer.InstallRequest(packageId) };
38+
await _bootstrapper.InstallTemplatePackagesAsync(installRequests);
39+
_isTemplateInstalled = true;
40+
}
41+
42+
public async Task<List<ITemplateInfo>> ListTemplatesAsync(string? version = null)
43+
{
44+
await EnsureTemplatePackageInstalled(version);
45+
var templates = await _bootstrapper.GetTemplatesAsync(CancellationToken.None);
46+
return templates.Where(t => t.MountPointUri.Contains(_templatePackageName, StringComparison.OrdinalIgnoreCase)).ToList();
47+
}
48+
49+
public async Task<ITemplateInfo?> GetTemplateByShortNameAsync(string shortName, string? version = null)
50+
{
51+
var templates = await ListTemplatesAsync(version);
52+
return templates.FirstOrDefault(t => t.ShortNameList.Contains(shortName, StringComparer.OrdinalIgnoreCase));
53+
}
54+
55+
public async Task<IReadOnlyList<ITemplateParameter>> ListParametersForTemplateAsync(string shortName, string? version = null)
56+
{
57+
var template = await GetTemplateByShortNameAsync(shortName, version);
58+
if (template == null) throw new InvalidOperationException($"Template '{shortName}' not found.");
59+
// Use ParameterDefinitions property (non-obsolete in v9+)
60+
return template.ParameterDefinitions;
61+
}
62+
63+
public async Task ScaffoldAsync(string shortName, string outputPath, IDictionary<string, string> parameters, string? version = null, CancellationToken cancellationToken = default)
64+
{
65+
await EnsureTemplatePackageInstalled(version);
66+
var template = await GetTemplateByShortNameAsync(shortName, version);
67+
if (template == null) throw new InvalidOperationException($"Template '{shortName}' not found.");
68+
var name = parameters.ContainsKey("name") ? parameters["name"] : "Component";
69+
await _bootstrapper.CreateAsync(
70+
template,
71+
name: name,
72+
outputPath: outputPath,
73+
parameters: parameters.ToDictionary(kv => kv.Key, kv => (string?)kv.Value),
74+
baselineName: null,
75+
cancellationToken: cancellationToken);
76+
}
77+
78+
public void Dispose()
79+
{
80+
_bootstrapper?.Dispose();
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)