Skip to content

Commit 40a1643

Browse files
author
Murat Genç
committed
feat: Add support for generating microservice, DDD, modular, GraphQL, and gRPC project structures
1 parent 76dcd7a commit 40a1643

File tree

2 files changed

+223
-1
lines changed

2 files changed

+223
-1
lines changed

tools/Relay.CLI.Tests/Integration/TemplateEndToEndTests.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,18 @@ public async Task GenerateModularProject_WithCustomModules_CreatesModuleStructur
241241
var result = await _generator.GenerateAsync("relay-modular", projectName, _testOutputPath, options);
242242

243243
// Assert
244+
if (!result.Success)
245+
{
246+
Console.WriteLine($"Error: {result.Message}");
247+
foreach (var error in result.Errors)
248+
{
249+
Console.WriteLine($" - {error}");
250+
}
251+
}
252+
244253
result.Success.Should().BeTrue();
245254
result.CreatedDirectories.Should().Contain(d => d.Contains("Modules"));
246-
255+
247256
foreach (var module in options.Modules!)
248257
{
249258
result.CreatedDirectories.Should().Contain(d => d.Contains(module));

tools/Relay.CLI/TemplateEngine/TemplateGenerator.cs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,21 @@ private async Task GenerateProjectFilesAsync(string templateId, string projectNa
239239
case "relay-webapi":
240240
await GenerateWebApiFilesAsync(projectName, outputPath, options, result);
241241
break;
242+
case "relay-microservice":
243+
await GenerateMicroserviceFilesAsync(projectName, outputPath, options, result);
244+
break;
245+
case "relay-ddd":
246+
await GenerateDddFilesAsync(projectName, outputPath, options, result);
247+
break;
248+
case "relay-modular":
249+
await GenerateModularFilesAsync(projectName, outputPath, options, result);
250+
break;
251+
case "relay-graphql":
252+
await GenerateGraphQLFilesAsync(projectName, outputPath, options, result);
253+
break;
254+
case "relay-grpc":
255+
await GenerateGrpcFilesAsync(projectName, outputPath, options, result);
256+
break;
242257
default:
243258
await GenerateBasicProjectAsync(projectName, outputPath, options, result);
244259
break;
@@ -599,4 +614,202 @@ FROM base AS final
599614
await File.WriteAllTextAsync(dockerfilePath, content);
600615
result.CreatedFiles.Add("Dockerfile");
601616
}
617+
618+
private async Task GenerateMicroserviceFilesAsync(string projectName, string outputPath, GenerationOptions options, GenerationResult result)
619+
{
620+
await GenerateSolutionFileAsync(projectName, outputPath, result);
621+
await GenerateBasicProjectAsync(projectName, outputPath, options, result);
622+
}
623+
624+
private async Task GenerateDddFilesAsync(string projectName, string outputPath, GenerationOptions options, GenerationResult result)
625+
{
626+
await GenerateSolutionFileAsync(projectName, outputPath, result);
627+
await GenerateApiProjectAsync(projectName, outputPath, options, result);
628+
await GenerateApplicationProjectAsync(projectName, outputPath, options, result);
629+
await GenerateDomainProjectAsync(projectName, outputPath, options, result);
630+
await GenerateInfrastructureProjectAsync(projectName, outputPath, options, result);
631+
}
632+
633+
private async Task GenerateModularFilesAsync(string projectName, string outputPath, GenerationOptions options, GenerationResult result)
634+
{
635+
await GenerateSolutionFileAsync(projectName, outputPath, result);
636+
637+
// Generate API project
638+
var apiProjectPath = Path.Combine(outputPath, "src", $"{projectName}.Api", $"{projectName}.Api.csproj");
639+
var apiContent = $@"<Project Sdk=""Microsoft.NET.Sdk.Web"">
640+
<PropertyGroup>
641+
<TargetFramework>{options.TargetFramework ?? "net8.0"}</TargetFramework>
642+
<Nullable>enable</Nullable>
643+
</PropertyGroup>
644+
645+
<ItemGroup>
646+
<PackageReference Include=""Relay"" Version=""*"" />
647+
</ItemGroup>
648+
</Project>
649+
";
650+
await File.WriteAllTextAsync(apiProjectPath, apiContent);
651+
result.CreatedFiles.Add($"src/{projectName}.Api/{projectName}.Api.csproj");
652+
653+
// Generate shared project
654+
var sharedProjectPath = Path.Combine(outputPath, "src", $"{projectName}.Shared", $"{projectName}.Shared.csproj");
655+
var sharedContent = $@"<Project Sdk=""Microsoft.NET.Sdk"">
656+
<PropertyGroup>
657+
<TargetFramework>{options.TargetFramework ?? "net8.0"}</TargetFramework>
658+
<Nullable>enable</Nullable>
659+
</PropertyGroup>
660+
</Project>
661+
";
662+
await File.WriteAllTextAsync(sharedProjectPath, sharedContent);
663+
result.CreatedFiles.Add($"src/{projectName}.Shared/{projectName}.Shared.csproj");
664+
665+
// Generate module projects
666+
var modules = options.Modules ?? new[] { "Catalog", "Orders" };
667+
foreach (var module in modules)
668+
{
669+
var moduleProjectPath = Path.Combine(outputPath, "src", $"{projectName}.Modules", module, $"{module}.csproj");
670+
var moduleContent = $@"<Project Sdk=""Microsoft.NET.Sdk"">
671+
<PropertyGroup>
672+
<TargetFramework>{options.TargetFramework ?? "net8.0"}</TargetFramework>
673+
<Nullable>enable</Nullable>
674+
</PropertyGroup>
675+
</Project>
676+
";
677+
await File.WriteAllTextAsync(moduleProjectPath, moduleContent);
678+
result.CreatedFiles.Add($"src/{projectName}.Modules/{module}/{module}.csproj");
679+
}
680+
}
681+
682+
private async Task GenerateGraphQLFilesAsync(string projectName, string outputPath, GenerationOptions options, GenerationResult result)
683+
{
684+
await GenerateSolutionFileAsync(projectName, outputPath, result);
685+
686+
// Generate main project with HotChocolate
687+
var projectPath = Path.Combine(outputPath, "src", projectName, $"{projectName}.csproj");
688+
var projectContent = $@"<Project Sdk=""Microsoft.NET.Sdk.Web"">
689+
<PropertyGroup>
690+
<TargetFramework>{options.TargetFramework ?? "net8.0"}</TargetFramework>
691+
<Nullable>enable</Nullable>
692+
</PropertyGroup>
693+
694+
<ItemGroup>
695+
<PackageReference Include=""HotChocolate.AspNetCore"" Version=""13.9.0"" />
696+
<PackageReference Include=""Relay"" Version=""*"" />
697+
</ItemGroup>
698+
</Project>
699+
";
700+
await File.WriteAllTextAsync(projectPath, projectContent);
701+
result.CreatedFiles.Add($"src/{projectName}/{projectName}.csproj");
702+
703+
// Generate Program.cs with GraphQL setup
704+
var programPath = Path.Combine(outputPath, "src", projectName, "Program.cs");
705+
var programContent = @"var builder = WebApplication.CreateBuilder(args);
706+
707+
builder.Services
708+
.AddGraphQLServer()
709+
.AddQueryType<Query>()
710+
.AddMutationType<Mutation>();
711+
712+
var app = builder.Build();
713+
714+
app.MapGraphQL();
715+
716+
app.Run();
717+
718+
public class Query
719+
{
720+
public string Hello() => ""Hello from GraphQL!"";
721+
}
722+
723+
public class Mutation
724+
{
725+
public string Echo(string message) => message;
726+
}
727+
";
728+
await File.WriteAllTextAsync(programPath, programContent);
729+
result.CreatedFiles.Add($"src/{projectName}/Program.cs");
730+
}
731+
732+
private async Task GenerateGrpcFilesAsync(string projectName, string outputPath, GenerationOptions options, GenerationResult result)
733+
{
734+
await GenerateSolutionFileAsync(projectName, outputPath, result);
735+
736+
// Generate main project with gRPC
737+
var projectPath = Path.Combine(outputPath, "src", projectName, $"{projectName}.csproj");
738+
var projectContent = $@"<Project Sdk=""Microsoft.NET.Sdk.Web"">
739+
<PropertyGroup>
740+
<TargetFramework>{options.TargetFramework ?? "net8.0"}</TargetFramework>
741+
<Nullable>enable</Nullable>
742+
</PropertyGroup>
743+
744+
<ItemGroup>
745+
<PackageReference Include=""Grpc.AspNetCore"" Version=""2.60.0"" />
746+
<PackageReference Include=""Relay"" Version=""*"" />
747+
</ItemGroup>
748+
749+
<ItemGroup>
750+
<Protobuf Include=""Protos\greet.proto"" GrpcServices=""Server"" />
751+
</ItemGroup>
752+
</Project>
753+
";
754+
await File.WriteAllTextAsync(projectPath, projectContent);
755+
result.CreatedFiles.Add($"src/{projectName}/{projectName}.csproj");
756+
757+
// Generate sample proto file
758+
var protoPath = Path.Combine(outputPath, "src", projectName, "Protos", "greet.proto");
759+
var protoContent = @"syntax = ""proto3"";
760+
761+
option csharp_namespace = ""GrpcService"";
762+
763+
package greet;
764+
765+
service Greeter {
766+
rpc SayHello (HelloRequest) returns (HelloReply);
767+
}
768+
769+
message HelloRequest {
770+
string name = 1;
771+
}
772+
773+
message HelloReply {
774+
string message = 1;
775+
}
776+
";
777+
await File.WriteAllTextAsync(protoPath, protoContent);
778+
result.CreatedFiles.Add($"src/{projectName}/Protos/greet.proto");
779+
780+
// Generate Program.cs
781+
var programPath = Path.Combine(outputPath, "src", projectName, "Program.cs");
782+
var programContent = @"var builder = WebApplication.CreateBuilder(args);
783+
784+
builder.Services.AddGrpc();
785+
786+
var app = builder.Build();
787+
788+
app.MapGrpcService<GreeterService>();
789+
790+
app.Run();
791+
";
792+
await File.WriteAllTextAsync(programPath, programContent);
793+
result.CreatedFiles.Add($"src/{projectName}/Program.cs");
794+
795+
// Generate sample service
796+
var servicePath = Path.Combine(outputPath, "src", projectName, "Services", "GreeterService.cs");
797+
var serviceContent = @"using Grpc.Core;
798+
799+
namespace GrpcService.Services;
800+
801+
public class GreeterService : Greeter.GreeterBase
802+
{
803+
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
804+
{
805+
return Task.FromResult(new HelloReply
806+
{
807+
Message = ""Hello "" + request.Name
808+
});
809+
}
810+
}
811+
";
812+
await File.WriteAllTextAsync(servicePath, serviceContent);
813+
result.CreatedFiles.Add($"src/{projectName}/Services/GreeterService.cs");
814+
}
602815
}

0 commit comments

Comments
 (0)