From d2a4639c0e5944b357007db23f6d06f2b43b20a1 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Sun, 2 Nov 2025 20:44:47 -0500 Subject: [PATCH] yes --- .DS_Store | Bin 6148 -> 8196 bytes .claude/settings.local.json | 4 +- .../ProtoFileSourceGenerator.cs | 7 +- .../WriteProtoFileTask.cs | 130 ++++++++++++++++++ .../build/Svrnty.CQRS.Grpc.Generators.targets | 29 +++- .../Protos/cqrs_services.proto | 21 +-- .../Svrnty.CQRS.Grpc.Sample.csproj | 3 + Svrnty.CQRS.Grpc/.DS_Store | Bin 0 -> 8196 bytes 8 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 Svrnty.CQRS.Grpc.Generators/WriteProtoFileTask.cs create mode 100644 Svrnty.CQRS.Grpc/.DS_Store diff --git a/.DS_Store b/.DS_Store index c02464cefbebb4ca26a4c4d153f42ce214ea1dfd..d5fd32295c2162f39bf65f41c5a3b8907f25fdff 100644 GIT binary patch literal 8196 zcmeHMU2GLa6rOL|!d_VE09D$0H(V$LA*GZb(}G}b|Ky(nm)hHclzZ>4baA^|?(V%U zKgPzy7Zr?88vl9HXyn0w8eb$)5q(sAz!)Eh8ubMeeNlPv%+402g@gw+n(ibs-^`hF zW_He=Z>Q&$F^2Y%xstJ3#+XJ`qL@j|4T_wXbyDGxq=FD+&se~YuqM{QcCp=)v|~Z+ zf!G7F2VxJz9*8~gU+@6k*|KOix%bs<+{PY=JuoFbAiobOsuI%?jwuX39n^#=0AVGG z!$N(^145frgy{&!6o#g>rpg`=nIcLtpv-ALCDjSj5soQT<_yZ5A=(*HhJtW+vP*^P z3~7aN8+#!3z+?}|++DB?E3+bVugl*vl*w--=`breez_y>czMPuGWlW0_Jii;FTpD+ ztENrY5;JD%w;8?JzW%-XfS6OZtG!1i4Keyivc4v1=h&>PuY@Z6n6OV0NeJf}3| z`U7HiA?FlIPA;(894BKRwMbU&7mA+a?DTwFF4LC{tkFQuO`6K~`(82V2p7xRw7$|3 zCEwa@(V}+Q>ZGOpnhEDd;`FR}^B3KmRB=z70xT5e!JXj{de z5!(y~`*ME4a!uPk*ju)JJMUO~+}w~g8;yy?#p>F+`a6?`aWmCyDQ_Px*})OfplRVA zsRf3<&!lJC?wN8IQ=HXk9ol{I0>juVXF7@&wPs|nbeW;=@#*j)7b!Na(DgkJ*{&&k z`3PjXdW~-MIJvy#2s5GfXRuB zqel~;X#+yoZrKj##n7uFDEV5$_^WA1FH_xybJn@A7dpBj-K@k3x*!a;igmJW>;Q%3 zF?NDI%TBQ~>?3xbeZjtFKd>w8R{+zHzzobp4eGH7jkp^vXvJEzqZ_-h8yW1wAZ#3h zk5L@OF&xKZcpOjS1Ww`^yoi_a3Qpm5yoED3i?{JUF5nY-ar<2k+)tp5uqO$4Auxcyx2I2h@1Hy=u0)CkC>*0jmajB2rGCv)K?}~Cy17! zLJb>}EeTCP4c6)F6GTu^sfLE0P9y{i2g#0PmqsKL+TRuZm)K?YJ^P7WrRbl9xv0ew zG*isqi%r-;Q7>b@2m3LA9P%(R1Q)|NO!0pl591Mx;ZZz6@qZFe;b}aF=kWqw!mD_V zIPeDEBnG^Lckv!Rz=t@Gk0UO8MNIe+KTnA6?c;H+5Wn0?beA(l*YVtgq&v=%>z9c~ z|9}VN-A|=f=Ko#v4T^sU2>lT^u?MD<2T<9W>1?MX-|X8fbL|w>15{;^dSePh6KcYA roG?ts39tRbkoqZ7l{V=J#}tx;%D?_Yz@LB7$M=7H|A#R+6`MZ*80Zig delta 399 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{MGjUEV6q~50D9Qwq2a9l zF*d8M<>U}m*0&Cd&(6us%kKud4hR^5z61kaC=H`}fD9b| +/// MSBuild task that extracts the auto-generated proto file content from the source generator +/// output and writes it to disk so Grpc.Tools can process it +/// +public class WriteProtoFileTask : Task +{ + /// + /// The project directory where we should look for generated files + /// + [Required] + public string ProjectDirectory { get; set; } = string.Empty; + + /// + /// The intermediate output path (typically obj/Debug/net10.0) + /// + [Required] + public string IntermediateOutputPath { get; set; } = string.Empty; + + /// + /// The output directory where the proto file should be written (typically Protos/) + /// + [Required] + public string OutputDirectory { get; set; } = string.Empty; + + /// + /// The name of the proto file to generate (typically cqrs_services.proto) + /// + [Required] + public string ProtoFileName { get; set; } = string.Empty; + + public override bool Execute() + { + try + { + Log.LogMessage(MessageImportance.High, + "Svrnty.CQRS.Grpc: Extracting auto-generated proto file..."); + + // Look for the generated C# file containing the proto content + // Source generators output to obj/Generated, not IntermediateOutputPath/Generated + var generatedFilePath = Path.Combine( + ProjectDirectory, + "obj", + "Generated", + "Svrnty.CQRS.Grpc.Generators", + "Svrnty.CQRS.Grpc.Generators.ProtoFileSourceGenerator", + "GeneratedProtoFile.g.cs" + ); + + if (!File.Exists(generatedFilePath)) + { + Log.LogWarning( + $"Generated proto file not found at {generatedFilePath}. " + + "The proto file may not have been generated yet. This is normal on first build."); + return true; // Don't fail the build, just skip + } + + // Read the generated C# file + var csContent = File.ReadAllText(generatedFilePath); + + // Extract the proto content using a more robust approach + // Looking for: public const string Content = @"..."; + var startMarker = "public const string Content = @\""; + var startIndex = csContent.IndexOf(startMarker); + + if (startIndex < 0) + { + Log.LogError($"Could not find Content property in {generatedFilePath}"); + return false; + } + + startIndex += startMarker.Length; + + // Find the closing "; - We need the LAST occurrence because the content contains escaped quotes + // The pattern is: Content = @"...content..."; + // where content has "" for literal quotes + var endMarker = "\";"; + + // Find where the next field starts or class ends to limit our search + var nextFieldOrEnd = csContent.IndexOf("\n }", startIndex); // End of class + if (nextFieldOrEnd < 0) + { + nextFieldOrEnd = csContent.Length; + } + + var endIndex = csContent.LastIndexOf(endMarker, nextFieldOrEnd, nextFieldOrEnd - startIndex); + + if (endIndex < 0 || endIndex < startIndex) + { + Log.LogError($"Could not find end of Content property in {generatedFilePath}"); + return false; + } + + // Extract and unescape doubled quotes + var protoContent = csContent.Substring(startIndex, endIndex - startIndex); + protoContent = protoContent.Replace("\"\"", "\""); + + Log.LogMessage(MessageImportance.High, + $"Extracted proto content length: {protoContent.Length} characters"); + + // Ensure output directory exists + var fullOutputPath = Path.Combine(ProjectDirectory, OutputDirectory); + Directory.CreateDirectory(fullOutputPath); + + // Write the proto file + var protoFilePath = Path.Combine(fullOutputPath, ProtoFileName); + File.WriteAllText(protoFilePath, protoContent); + + Log.LogMessage(MessageImportance.High, + $"Svrnty.CQRS.Grpc: Successfully generated proto file at {protoFilePath}"); + + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true); + return false; + } + } +} diff --git a/Svrnty.CQRS.Grpc.Generators/build/Svrnty.CQRS.Grpc.Generators.targets b/Svrnty.CQRS.Grpc.Generators/build/Svrnty.CQRS.Grpc.Generators.targets index 6f77d41..e31cc0b 100644 --- a/Svrnty.CQRS.Grpc.Generators/build/Svrnty.CQRS.Grpc.Generators.targets +++ b/Svrnty.CQRS.Grpc.Generators/build/Svrnty.CQRS.Grpc.Generators.targets @@ -6,17 +6,32 @@ cqrs_services.proto - - - + + + <_GeneratorsAssemblyPath Condition="Exists('$(MSBuildThisFileDirectory)\..\analyzers\dotnet\cs\Svrnty.CQRS.Grpc.Generators.dll')">$(MSBuildThisFileDirectory)\..\analyzers\dotnet\cs\Svrnty.CQRS.Grpc.Generators.dll + <_GeneratorsAssemblyPath Condition="'$(_GeneratorsAssemblyPath)' == '' AND Exists('$(MSBuildThisFileDirectory)\..\bin\Debug\netstandard2.0\Svrnty.CQRS.Grpc.Generators.dll')">$(MSBuildThisFileDirectory)\..\bin\Debug\netstandard2.0\Svrnty.CQRS.Grpc.Generators.dll + <_GeneratorsAssemblyPath Condition="'$(_GeneratorsAssemblyPath)' == '' AND Exists('$(MSBuildThisFileDirectory)\..\bin\Release\netstandard2.0\Svrnty.CQRS.Grpc.Generators.dll')">$(MSBuildThisFileDirectory)\..\bin\Release\netstandard2.0\Svrnty.CQRS.Grpc.Generators.dll + + + + - - - $(MSBuildProjectDirectory) - + + + + + + + diff --git a/Svrnty.CQRS.Grpc.Sample/Protos/cqrs_services.proto b/Svrnty.CQRS.Grpc.Sample/Protos/cqrs_services.proto index 1857eeb..4cab121 100644 --- a/Svrnty.CQRS.Grpc.Sample/Protos/cqrs_services.proto +++ b/Svrnty.CQRS.Grpc.Sample/Protos/cqrs_services.proto @@ -6,46 +6,48 @@ package cqrs; // Command service for CQRS operations service CommandService { - // Adds a new user and returns the user ID + // AddUserCommand operation rpc AddUser (AddUserCommandRequest) returns (AddUserCommandResponse); - // Removes a user + // RemoveUserCommand operation rpc RemoveUser (RemoveUserCommandRequest) returns (RemoveUserCommandResponse); + } // Query service for CQRS operations service QueryService { - // Fetches a user by ID + // FetchUserQuery operation rpc FetchUser (FetchUserQueryRequest) returns (FetchUserQueryResponse); + } -// Request message for adding a user +// Request message for AddUserCommand message AddUserCommandRequest { string name = 1; string email = 2; int32 age = 3; } -// Response message containing the added user ID +// Response message for AddUserCommand message AddUserCommandResponse { int32 result = 1; } -// Request message for removing a user +// Request message for RemoveUserCommand message RemoveUserCommandRequest { int32 user_id = 1; } -// Response message for remove user (empty) +// Response message for RemoveUserCommand message RemoveUserCommandResponse { } -// Request message for fetching a user +// Request message for FetchUserQuery message FetchUserQueryRequest { int32 user_id = 1; } -// Response message containing the user +// Response message for FetchUserQuery message FetchUserQueryResponse { User result = 1; } @@ -56,3 +58,4 @@ message User { string name = 2; string email = 3; } + diff --git a/Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj b/Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj index 50b1100..8b4beef 100644 --- a/Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj +++ b/Svrnty.CQRS.Grpc.Sample/Svrnty.CQRS.Grpc.Sample.csproj @@ -32,4 +32,7 @@ + + + diff --git a/Svrnty.CQRS.Grpc/.DS_Store b/Svrnty.CQRS.Grpc/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0ca91ffa8a2a5f3a467bd8b2c6bfa09c1fce019e GIT binary patch literal 8196 zcmeHMU2GIp6u#fIzziMe07Vwu2@8cFWP$Q$%b(f)Ddn%gwsc#5mf4+=4oqjt&g`~O z8XFT|_%l9f{O3udkp}}xe33*&^ik0SV|*ZL)E7+jMdiUWb7%Q!X?!t8ac(mA+;i?Z z_s%)}RT5K1bEg6x^wpR=4CaTjy_W3(ed z6oDuLQ3Rq0L=lK0@Nb9!?b&`2$JzIJZ&XJSh$8U6jDWvBBR-f&h;0_>M1lW{O4xPO1jpde_ z`E5i+I@iy=`Ks)VG`UKiqIPAvd-lj#WqOHTyS?8t9oyY)6kMWNrP?L;+VYOQqhR>` zU3teU_StS$nU>eAe9_W8qtUX`<}rh0)o#AvSk?~5HT_|_GoCT%`F&%$GCi(S@S20c zGA51BwUnZ3>@sLjvt+c;&~D8r*C3snI&1ENhnB9a-;io;+qUy+wK8q`j2c<)p*Y%} z`M7BmItNVM>+ROuykYC6eXy%!x@OKYI&7`an1-YzU9FDIp7UT_RmW4!6m#a0qUjw~ zYDF=aBQZ~v_v-Xc+Z^4WMOQ9}G!D(aaGt8}@q3yJ2Gx3Kuy~0ocev!dqJ@gfmdkR- zVFG~S`maEyt5(Zur={f#OVK4hPs3VS?JDv3D_cV0#u{18g#cPwSv(;KmZJ-n@JjcQj+^dt-9$sK_^9HJ9*LV57(&cfe&+H zv#Zs#+9@iZiCIN4?UG5Lr_aibnO>JrkZ`NKiE(NV1(6y;LE|zFhQ`t(^!nU#l ztiXoYY4#jD%g(cp*k$$w`-c69j|+GQ@8b$S!Ke5f*YPdB!}qv>-|+`- z3KN7`LQI$~%omcvQel};FEj}2g!RI9pX4iG@p+uWs1bcng9Yvt^jx3H|`_cjZZd_y7^3 z(0Y~UpggynpQDqplTyVP6#a}PV7e(8gkIl2OIr3LMT6uVLX8mJc*Np@-uiE&)|8yfEV#H zUc>8z^f&Pq;rv}(#C!MvAL24T4iWz~;r%E4G77`n%IM`3zuv}hDP6EF$394sGUD%6 zzAw27?p4e`|F@2R|9@Zl9yt|7Ac_Ew04iJ3Elt!OZ|(EX+6j6N(Bl_hZ-mppgsQtP o0*Jr$hat5Sr0UzGLOQ}JNvQne7Xg3$Lp$34qy0a?=KWm#2_TVXqW}N^ literal 0 HcmV?d00001