diff --git a/docs/guide/codegen.md b/docs/guide/codegen.md
index 82181e1eb..ca1e7ddd2 100644
--- a/docs/guide/codegen.md
+++ b/docs/guide/codegen.md
@@ -203,7 +203,7 @@ As of Wolverine 5.0, you now have the ability to better control the usage of the
code generation to potentially avoid unwanted usage:
-
+
```cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
@@ -225,7 +225,7 @@ builder.UseWolverine(opts =>
opts.ServiceLocationPolicy = ServiceLocationPolicy.NotAllowed;
});
```
-snippet source | anchor
+snippet source | anchor
::: note
@@ -393,3 +393,62 @@ Which will use:
1. `TypeLoadMode.Dynamic` when the .NET environment is "Development" and dynamically generate types on the first usage
2. `TypeLoadMode.Static` for other .NET environments for optimized cold start times
+
+## Customizing the Generated Code Output Path
+
+By default, Wolverine writes generated code to `Internal/Generated` under your project's content root.
+For Console applications or non-standard project structures, you may need to customize this path.
+
+### Using CritterStackDefaults
+
+You can configure the output path globally for all Critter Stack tools:
+
+
+
+```cs
+var builder = Host.CreateApplicationBuilder();
+builder.Services.CritterStackDefaults(opts =>
+{
+ // Set a custom output path for generated code
+ opts.GeneratedCodeOutputPath = "/path/to/your/project/Internal/Generated";
+});
+```
+snippet source | anchor
+
+
+### Auto-Resolving Project Root for Console Apps
+
+Console applications often have `ContentRootPath` pointing to the `bin` folder, which causes
+generated code to be written to the wrong location. Enable automatic project root resolution:
+
+
+
+```cs
+var builder = Host.CreateApplicationBuilder();
+builder.Services.CritterStackDefaults(opts =>
+{
+ // Automatically find the project root by looking for .csproj/.sln files
+ // Useful for Console apps where ContentRootPath defaults to bin folder
+ opts.AutoResolveProjectRoot = true;
+});
+```
+snippet source | anchor
+
+
+### Direct Wolverine Configuration
+
+You can also configure the path directly on Wolverine:
+
+
+
+```cs
+var builder = Host.CreateApplicationBuilder();
+builder.UseWolverine(opts =>
+{
+ opts.CodeGeneration.GeneratedCodeOutputPath = "/path/to/output";
+});
+```
+snippet source | anchor
+
+
+Note that explicit Wolverine configuration takes precedence over `CritterStackDefaults`.
diff --git a/src/Samples/DocumentationSamples/CodegenUsage.cs b/src/Samples/DocumentationSamples/CodegenUsage.cs
index 3471d1fbb..406702622 100644
--- a/src/Samples/DocumentationSamples/CodegenUsage.cs
+++ b/src/Samples/DocumentationSamples/CodegenUsage.cs
@@ -83,4 +83,46 @@ public async Task use_optimized_workflow()
#endregion
}
+
+ public async Task configure_generated_code_output_path()
+ {
+ #region sample_configure_generated_code_output_path
+
+ var builder = Host.CreateApplicationBuilder();
+ builder.Services.CritterStackDefaults(opts =>
+ {
+ // Set a custom output path for generated code
+ opts.GeneratedCodeOutputPath = "/path/to/your/project/Internal/Generated";
+ });
+
+ #endregion
+ }
+
+ public async Task auto_resolve_project_root()
+ {
+ #region sample_auto_resolve_project_root
+
+ var builder = Host.CreateApplicationBuilder();
+ builder.Services.CritterStackDefaults(opts =>
+ {
+ // Automatically find the project root by looking for .csproj/.sln files
+ // Useful for Console apps where ContentRootPath defaults to bin folder
+ opts.AutoResolveProjectRoot = true;
+ });
+
+ #endregion
+ }
+
+ public async Task direct_wolverine_output_path()
+ {
+ #region sample_direct_wolverine_output_path
+
+ var builder = Host.CreateApplicationBuilder();
+ builder.UseWolverine(opts =>
+ {
+ opts.CodeGeneration.GeneratedCodeOutputPath = "/path/to/output";
+ });
+
+ #endregion
+ }
}
\ No newline at end of file
diff --git a/src/Testing/CoreTests/Configuration/generated_code_output_path_configuration.cs b/src/Testing/CoreTests/Configuration/generated_code_output_path_configuration.cs
new file mode 100644
index 000000000..c350f64a4
--- /dev/null
+++ b/src/Testing/CoreTests/Configuration/generated_code_output_path_configuration.cs
@@ -0,0 +1,99 @@
+using JasperFx;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+namespace CoreTests.Configuration;
+
+public class generated_code_output_path_configuration
+{
+ [Fact]
+ public void wolverine_options_should_use_jasperfx_generated_code_output_path()
+ {
+ var jasperfx = new JasperFxOptions
+ {
+ GeneratedCodeOutputPath = "/custom/path/Generated",
+ };
+
+ var wolverineOptions = new WolverineOptions();
+ wolverineOptions.ReadJasperFxOptions(jasperfx);
+
+ wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
+ .ShouldBe("/custom/path/Generated");
+ }
+
+ [Fact]
+ public void wolverine_options_should_not_override_explicit_generated_code_output_path()
+ {
+ var jasperfx = new JasperFxOptions
+ {
+ GeneratedCodeOutputPath = "/jasperfx/path",
+ };
+
+ var wolverineOptions = new WolverineOptions();
+ wolverineOptions.CodeGeneration.GeneratedCodeOutputPath = "/explicit/path";
+ wolverineOptions.ReadJasperFxOptions(jasperfx);
+
+ // Should keep explicit setting
+ wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
+ .ShouldBe("/explicit/path");
+ }
+
+ [Fact]
+ public async Task critter_stack_defaults_generated_code_output_path_flows_to_wolverine()
+ {
+ using var host = await Host.CreateDefaultBuilder()
+ .ConfigureServices(services =>
+ {
+ services.CritterStackDefaults(opts =>
+ {
+ opts.GeneratedCodeOutputPath = "/test/output/path";
+ });
+ })
+ .UseWolverine()
+ .StartAsync();
+
+ var wolverineOptions = host.Services.GetRequiredService();
+ wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
+ .ShouldBe("/test/output/path");
+ }
+
+ [Fact]
+ public async Task explicit_wolverine_path_takes_precedence_over_critter_stack_defaults()
+ {
+ using var host = await Host.CreateDefaultBuilder()
+ .ConfigureServices(services =>
+ {
+ services.CritterStackDefaults(opts =>
+ {
+ opts.GeneratedCodeOutputPath = "/jasperfx/path";
+ });
+ })
+ .UseWolverine(opts =>
+ {
+ opts.CodeGeneration.GeneratedCodeOutputPath = "/explicit/wolverine/path";
+ })
+ .StartAsync();
+
+ var wolverineOptions = host.Services.GetRequiredService();
+ wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
+ .ShouldBe("/explicit/wolverine/path");
+ }
+
+ [Fact]
+ public void wolverine_options_should_not_copy_null_generated_code_output_path()
+ {
+ var jasperfx = new JasperFxOptions
+ {
+ // GeneratedCodeOutputPath is null by default
+ };
+
+ var wolverineOptions = new WolverineOptions();
+ var defaultPath = wolverineOptions.CodeGeneration.GeneratedCodeOutputPath;
+ wolverineOptions.ReadJasperFxOptions(jasperfx);
+
+ // Should keep the default path when JasperFxOptions has null
+ wolverineOptions.CodeGeneration.GeneratedCodeOutputPath
+ .ShouldBe(defaultPath);
+ }
+}
diff --git a/src/Wolverine/HostBuilderExtensions.cs b/src/Wolverine/HostBuilderExtensions.cs
index eb548d72d..9f3127fb4 100644
--- a/src/Wolverine/HostBuilderExtensions.cs
+++ b/src/Wolverine/HostBuilderExtensions.cs
@@ -125,22 +125,43 @@ internal static IServiceCollection AddWolverine(this IServiceCollection services
var environment = s.GetService();
var directory = environment?.ContentRootPath ?? AppContext.BaseDirectory;
-#if DEBUG
- if (directory.EndsWith("Debug", StringComparison.OrdinalIgnoreCase))
- {
- directory = directory.ParentDirectory()!.ParentDirectory();
- }
- else if (directory.ParentDirectory()!.EndsWith("Debug", StringComparison.OrdinalIgnoreCase))
+ // Don't correct for the path if it's already been set (from JasperFxOptions or user)
+ if (options.CodeGeneration.GeneratedCodeOutputPath == "Internal/Generated")
{
- directory = directory.ParentDirectory()!.ParentDirectory()!.ParentDirectory();
- }
+#if DEBUG
+ // In DEBUG builds, try to resolve project root like JasperFx does during codegen
+ if (jasperfx.AutoResolveProjectRoot)
+ {
+ var resolvedRoot = JasperFxOptions.ResolveProjectRoot(directory);
+ if (resolvedRoot != null)
+ {
+ directory = resolvedRoot;
+ }
+ }
+ else
+ {
+ // Legacy behavior for backward compatibility when AutoResolveProjectRoot is false
+ if (directory.EndsWith("Debug", StringComparison.OrdinalIgnoreCase))
+ {
+ directory = directory.ParentDirectory()!.ParentDirectory();
+ }
+ else if (directory.ParentDirectory()!.EndsWith("Debug", StringComparison.OrdinalIgnoreCase))
+ {
+ directory = directory.ParentDirectory()!.ParentDirectory()!.ParentDirectory();
+ }
+ }
#endif
- // Don't correct for the path if it's already been set
- if (options.CodeGeneration.GeneratedCodeOutputPath == "Internal/Generated")
- {
- options.CodeGeneration.GeneratedCodeOutputPath =
- directory!.AppendPath("Internal", "Generated");
+ // Use JasperFxOptions path if set, otherwise use the resolved directory
+ if (jasperfx.GeneratedCodeOutputPath != null)
+ {
+ options.CodeGeneration.GeneratedCodeOutputPath = jasperfx.GeneratedCodeOutputPath;
+ }
+ else
+ {
+ options.CodeGeneration.GeneratedCodeOutputPath =
+ directory!.AppendPath("Internal", "Generated");
+ }
}
return options;
diff --git a/src/Wolverine/WolverineOptions.cs b/src/Wolverine/WolverineOptions.cs
index 1a5d3037a..a1c95a3ca 100644
--- a/src/Wolverine/WolverineOptions.cs
+++ b/src/Wolverine/WolverineOptions.cs
@@ -447,6 +447,12 @@ internal void ReadJasperFxOptions(JasperFxOptions jasperfx)
{
_autoBuildMessageStorageOnStartup = jasperfx.ActiveProfile.ResourceAutoCreate;
}
+
+ // Propagate GeneratedCodeOutputPath from JasperFxOptions if not explicitly set
+ if (CodeGeneration.GeneratedCodeOutputPath == "Internal/Generated" && jasperfx.GeneratedCodeOutputPath != null)
+ {
+ CodeGeneration.GeneratedCodeOutputPath = jasperfx.GeneratedCodeOutputPath;
+ }
}
public void RegisterMessageType(Type messageType, string messageAlias)