diff --git a/docs/guide/command-line.md b/docs/guide/command-line.md
index d59574063..d56c669f2 100644
--- a/docs/guide/command-line.md
+++ b/docs/guide/command-line.md
@@ -71,8 +71,9 @@ The available commands are:
help List all the available commands
resources Check, setup, or teardown stateful resources of this system
run Start and run this .Net application
- storage Administer the Wolverine message storage
-
+ storage Administer the Wolverine message storage
+ wolverine-diagnostics Wolverine diagnostics tools for inspecting generated code and runtime behavior
+
Use dotnet run -- ? [command name] or dotnet run -- help [command name] to see usage help about a specific command
@@ -118,6 +119,80 @@ to help you troubleshoot issues in the future.
This functionality was originally built for consumption in the "CritterWatch" add on tool, but was requested by a [JasperFx Software](https://jasperfx.net)
client to provide a mechanism to detect any unintentional changes to Wolverine application configuration.
+## CLI Commands Work Without External Connectivity
+
+::: tip
+This applies to `codegen write`, `codegen preview`, `describe`, and OpenAPI generation tools such as
+`GetDocument.Insider` (Microsoft.Extensions.ApiDescription.Server). You do **not** need a running
+database or message broker for these commands to succeed.
+:::
+
+Wolverine automatically detects when it is running in a metadata-only CLI mode and suppresses
+persistence and transport initialization. No database connections or message broker connections
+are opened. This allows commands like `codegen` and `describe` to work safely in CI pipelines or
+developer machines that do not have external infrastructure available.
+
+Detection is based on two signals:
+
+1. **`DynamicCodeBuilder.WithinCodegenCommand`** — set by JasperFx when the `codegen` command is
+ used, either via `dotnet run -- codegen ...` or the `--start` flag.
+2. **`ASPNETCORE_HOSTINGSTARTUPASSEMBLIES` environment variable** — contains `"GetDocument"` when
+ OpenAPI generation tools like `GetDocument.Insider` start the host.
+
+When either condition is true, Wolverine applies the equivalent of "lightweight mode":
+external transports are stubbed out, durability agents are disabled, and the durability
+mode is set to `MediatorOnly`.
+
+If you need to explicitly disable persistence initialization for other tooling (e.g., your own
+OpenAPI generation pipeline), you can use the `DisableAllWolverineMessagePersistence()` extension:
+
+```csharp
+// In Program.cs or Startup.cs, guard with an environment check for your tooling
+builder.Services.DisableAllWolverineMessagePersistence();
+```
+
+## Wolverine Diagnostics Commands
+
+The `wolverine-diagnostics` command is an extensible parent command for deeper Wolverine-specific
+inspection tools. Currently it exposes one sub-command: **`codegen-preview`**.
+
+### codegen-preview
+
+Preview the full generated adapter code for a **specific** message handler or HTTP endpoint without
+generating all handlers at once. This is useful when you want to understand exactly what middleware,
+dependency resolution, or transaction wrapping Wolverine applies to a single entry point.
+
+**Preview a message handler** (accepts fully-qualified name, short class name, or handler class name):
+
+```bash
+# Fully-qualified message type
+dotnet run -- wolverine-diagnostics codegen-preview --handler MyApp.Orders.CreateOrder
+
+# Short message type name (fuzzy match)
+dotnet run -- wolverine-diagnostics codegen-preview --handler CreateOrder
+
+# Handler class name
+dotnet run -- wolverine-diagnostics codegen-preview --handler CreateOrderHandler
+```
+
+**Preview an HTTP endpoint** (requires Wolverine.HTTP; format: `"METHOD /path"`):
+
+```bash
+dotnet run -- wolverine-diagnostics codegen-preview --route "POST /api/orders"
+dotnet run -- wolverine-diagnostics codegen-preview --route "GET /api/orders/{id}"
+```
+
+The output includes the full generated class — the `Handle` or `HandleAsync` override, all
+middleware calls in order, dependency resolution from the IoC container, and any
+transaction-wrapping frames. This is identical to what `codegen preview` outputs, but scoped to
+exactly one handler so the signal-to-noise ratio is much higher.
+
+::: tip
+`wolverine-diagnostics` works without database or message-broker connectivity for the same reason
+as `codegen preview`: Wolverine automatically detects CLI codegen mode and stubs out persistence
+and transports.
+:::
+
## Other Highlights
* See the [code generation support](./codegen)
diff --git a/src/Http/Wolverine.Http/WolverineHttpEndpointRouteBuilderExtensions.cs b/src/Http/Wolverine.Http/WolverineHttpEndpointRouteBuilderExtensions.cs
index 9c73bc1e2..bd64ae1bf 100644
--- a/src/Http/Wolverine.Http/WolverineHttpEndpointRouteBuilderExtensions.cs
+++ b/src/Http/Wolverine.Http/WolverineHttpEndpointRouteBuilderExtensions.cs
@@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using JasperFx;
+using JasperFx.CodeGeneration;
using JasperFx.Core.IoC;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
@@ -210,7 +211,7 @@ public static void MapWolverineEndpoints(this IEndpointRouteBuilder endpoints,
options.Policies.Add(new ProblemDetailsFromMiddleware());
- if (Environment.CommandLine.Contains("codegen", StringComparison.OrdinalIgnoreCase))
+ if (DynamicCodeBuilder.WithinCodegenCommand)
{
options.WarmUpRoutes = RouteWarmup.Lazy;
}
diff --git a/src/Testing/CoreTests/Bugs/Bug_2471_codegen_without_connectivity.cs b/src/Testing/CoreTests/Bugs/Bug_2471_codegen_without_connectivity.cs
new file mode 100644
index 000000000..11e82b836
--- /dev/null
+++ b/src/Testing/CoreTests/Bugs/Bug_2471_codegen_without_connectivity.cs
@@ -0,0 +1,89 @@
+using JasperFx;
+using JasperFx.CodeGeneration;
+using JasperFx.CodeGeneration.Model;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Wolverine.Tracking;
+using Xunit;
+
+namespace CoreTests.Bugs;
+
+///
+/// Smoke tests for GitHub issue #2471: codegen and OpenAPI CLI commands should work
+/// without database/transport connectivity.
+///
+public class Bug_2471_codegen_without_connectivity
+{
+ [Fact]
+ public void codegen_preview_works_without_database_connection()
+ {
+ // Simulate the codegen command setting WithinCodegenCommand = true before building host
+ DynamicCodeBuilder.WithinCodegenCommand = true;
+
+ try
+ {
+ // Build but do not start the host — the codegen command does this by default
+ // (without --start flag). No DB or transport connectivity is needed.
+ var host = Host.CreateDefaultBuilder()
+ .UseWolverine(opts =>
+ {
+ opts.Discovery.DisableConventionalDiscovery()
+ .IncludeType(typeof(SimpleCodegenHandler2471));
+ })
+ .Build();
+
+ var collections = host.Services.GetServices().ToArray();
+ collections.ShouldNotBeEmpty("Expected at least one ICodeFileCollection from Wolverine");
+
+ var builder = new DynamicCodeBuilder(host.Services, collections)
+ {
+ ServiceVariableSource = host.Services.GetService()
+ };
+
+ // Should not throw even with no real database or transport configured
+ var code = builder.GenerateAllCode();
+ code.ShouldNotBeNullOrEmpty();
+ }
+ finally
+ {
+ DynamicCodeBuilder.WithinCodegenCommand = false;
+ }
+ }
+
+ [Fact]
+ public async Task host_startup_applies_lightweight_mode_automatically_during_codegen_command()
+ {
+ DynamicCodeBuilder.WithinCodegenCommand = true;
+
+ try
+ {
+ using var host = await Host.CreateDefaultBuilder()
+ .UseWolverine(opts =>
+ {
+ opts.Discovery.DisableConventionalDiscovery()
+ .IncludeType(typeof(SimpleCodegenHandler2471));
+ })
+ .StartAsync();
+
+ var runtime = host.GetRuntime();
+
+ // Lightweight mode should have been applied automatically
+ runtime.Options.LightweightMode.ShouldBeTrue();
+ runtime.Options.ExternalTransportsAreStubbed.ShouldBeTrue();
+ runtime.Options.Durability.DurabilityAgentEnabled.ShouldBeFalse();
+ }
+ finally
+ {
+ DynamicCodeBuilder.WithinCodegenCommand = false;
+ }
+ }
+}
+
+public record SimpleCodegenMessage2471;
+
+public static class SimpleCodegenHandler2471
+{
+ public static void Handle(SimpleCodegenMessage2471 message)
+ {
+ }
+}
diff --git a/src/Testing/CoreTests/Diagnostics/WolverineDiagnosticsCommandTests.cs b/src/Testing/CoreTests/Diagnostics/WolverineDiagnosticsCommandTests.cs
new file mode 100644
index 000000000..cc98347de
--- /dev/null
+++ b/src/Testing/CoreTests/Diagnostics/WolverineDiagnosticsCommandTests.cs
@@ -0,0 +1,156 @@
+using JasperFx;
+using JasperFx.CodeGeneration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Shouldly;
+using Wolverine.Diagnostics;
+using Wolverine.Runtime.Handlers;
+using Xunit;
+
+namespace CoreTests.Diagnostics;
+
+///
+/// Unit and smoke tests for the WolverineDiagnosticsCommand added in GitHub issue #2467.
+///
+public class WolverineDiagnosticsCommandTests
+{
+ // ── RouteInputToFileName ──────────────────────────────────────────────────
+
+ [Theory]
+ [InlineData("POST /api/orders", "POST_api_orders")]
+ [InlineData("GET /api/orders/{id}", "GET_api_orders_id")]
+ [InlineData("DELETE /api/orders/{id}", "DELETE_api_orders_id")]
+ [InlineData("GET /", "GET")]
+ [InlineData("PUT /api/v1/users/{userId}/roles", "PUT_api_v1_users_userId_roles")]
+ [InlineData("/api/orders", "api_orders")] // no HTTP method prefix
+ [InlineData("POST /api/some-path", "POST_api_some_path")] // hyphens → underscores
+ public void route_input_to_file_name(string input, string expected)
+ {
+ WolverineDiagnosticsCommand.RouteInputToFileName(input).ShouldBe(expected);
+ }
+
+ // ── FindHandlerChain — exact and fuzzy matching ──────────────────────────
+
+ [Fact]
+ public void find_handler_chain_by_exact_full_name()
+ {
+ var chains = BuildTestChains();
+ var found = WolverineDiagnosticsCommand.FindHandlerChain(
+ "CoreTests.Diagnostics.DiagnosticsTestMessage", chains);
+ found.ShouldNotBeNull();
+ found.MessageType.ShouldBe(typeof(DiagnosticsTestMessage));
+ }
+
+ [Fact]
+ public void find_handler_chain_by_short_name()
+ {
+ var chains = BuildTestChains();
+ var found = WolverineDiagnosticsCommand.FindHandlerChain("DiagnosticsTestMessage", chains);
+ found.ShouldNotBeNull();
+ found.MessageType.ShouldBe(typeof(DiagnosticsTestMessage));
+ }
+
+ [Fact]
+ public void find_handler_chain_by_handler_class_name()
+ {
+ var chains = BuildTestChains();
+ var found = WolverineDiagnosticsCommand.FindHandlerChain("DiagnosticsTestHandler", chains);
+ found.ShouldNotBeNull();
+ found.MessageType.ShouldBe(typeof(DiagnosticsTestMessage));
+ }
+
+ [Fact]
+ public void find_handler_chain_fuzzy_contains_message_type()
+ {
+ var chains = BuildTestChains();
+ var found = WolverineDiagnosticsCommand.FindHandlerChain("TestMessage", chains);
+ found.ShouldNotBeNull();
+ found.MessageType.ShouldBe(typeof(DiagnosticsTestMessage));
+ }
+
+ [Fact]
+ public void find_handler_chain_returns_null_for_unknown()
+ {
+ var chains = BuildTestChains();
+ var found = WolverineDiagnosticsCommand.FindHandlerChain("NonExistentXyzHandler", chains);
+ found.ShouldBeNull();
+ }
+
+ private static HandlerChain[] BuildTestChains()
+ {
+ // Compile a real HandlerGraph so we get genuine HandlerChain objects.
+ DynamicCodeBuilder.WithinCodegenCommand = true;
+ try
+ {
+ var host = Host.CreateDefaultBuilder()
+ .UseWolverine(opts =>
+ {
+ opts.Discovery
+ .DisableConventionalDiscovery()
+ .IncludeType(typeof(DiagnosticsTestHandler));
+ })
+ .Build();
+
+ // Accessing ICodeFileCollection triggers HandlerGraph.Compile()
+ host.Services.GetServices().ToArray();
+
+ var graph = host.Services.GetRequiredService();
+ return graph.AllChains().ToArray();
+ }
+ finally
+ {
+ DynamicCodeBuilder.WithinCodegenCommand = false;
+ }
+ }
+
+ // ── Full codegen-preview smoke test ──────────────────────────────────────
+
+ [Fact]
+ public async Task codegen_preview_generates_code_for_handler()
+ {
+ DynamicCodeBuilder.WithinCodegenCommand = true;
+ try
+ {
+ using var host = await Host.CreateDefaultBuilder()
+ .UseWolverine(opts =>
+ {
+ opts.Discovery
+ .DisableConventionalDiscovery()
+ .IncludeType(typeof(DiagnosticsTestHandler));
+ })
+ .StartAsync();
+
+ var services = host.Services;
+ var serviceVariableSource = services.GetService();
+ var graph = services.GetRequiredService();
+ var chains = graph.AllChains().ToArray();
+
+ var chain = WolverineDiagnosticsCommand.FindHandlerChain("DiagnosticsTestMessage", chains);
+ chain.ShouldNotBeNull();
+
+ // Mimic what the command does: generate code for a single chain
+ var generatedAssembly = graph.StartAssembly(graph.Rules);
+ ((JasperFx.CodeGeneration.ICodeFile)chain).AssembleTypes(generatedAssembly);
+ var code = generatedAssembly.GenerateCode(serviceVariableSource);
+
+ code.ShouldNotBeNullOrEmpty();
+ code.ShouldContain("DiagnosticsTestHandler");
+ }
+ finally
+ {
+ DynamicCodeBuilder.WithinCodegenCommand = false;
+ }
+ }
+}
+
+// ── Test fixtures ────────────────────────────────────────────────────────────
+
+public record DiagnosticsTestMessage(string Text);
+
+public static class DiagnosticsTestHandler
+{
+ public static void Handle(DiagnosticsTestMessage message)
+ {
+ // intentionally empty — used only for code-generation testing
+ }
+}
diff --git a/src/Wolverine/Diagnostics/WolverineDiagnosticsCommand.cs b/src/Wolverine/Diagnostics/WolverineDiagnosticsCommand.cs
new file mode 100644
index 000000000..28f859a62
--- /dev/null
+++ b/src/Wolverine/Diagnostics/WolverineDiagnosticsCommand.cs
@@ -0,0 +1,306 @@
+using JasperFx;
+using JasperFx.CodeGeneration;
+using JasperFx.CodeGeneration.Model;
+using JasperFx.CommandLine;
+using JasperFx.Core;
+using Microsoft.Extensions.DependencyInjection;
+using Spectre.Console;
+using Wolverine.Runtime.Handlers;
+
+namespace Wolverine.Diagnostics;
+
+public class WolverineDiagnosticsInput : NetCoreInput
+{
+ [Description("Diagnostics sub-command to execute. Valid values: codegen-preview")]
+ public string Action { get; set; } = "codegen-preview";
+
+ [FlagAlias("handler", 'h')]
+ [Description(
+ "For codegen-preview: preview generated code for a specific message handler. " +
+ "Accepts a fully-qualified message type name (e.g. MyApp.Orders.CreateOrder), " +
+ "a short class name (e.g. CreateOrder), or a handler class name (e.g. CreateOrderHandler).")]
+ public string HandlerFlag { get; set; } = string.Empty;
+
+ [FlagAlias("route", 'r')]
+ [Description(
+ "For codegen-preview: preview generated code for a specific HTTP endpoint. " +
+ "Format: 'METHOD /path' (e.g. 'POST /api/orders' or 'GET /api/orders/{id}').")]
+ public string RouteFlag { get; set; } = string.Empty;
+}
+
+///
+/// Parent command for Wolverine diagnostics sub-commands. Currently supports:
+///
+/// -
+/// codegen-preview --handler <type> — show generated handler adapter code
+///
+/// -
+/// codegen-preview --route "METHOD /path" — show generated HTTP endpoint code
+///
+///
+///
+[Description("Wolverine diagnostics tools for inspecting generated code and runtime behavior",
+ Name = "wolverine-diagnostics")]
+public class WolverineDiagnosticsCommand : JasperFxAsyncCommand
+{
+ public WolverineDiagnosticsCommand()
+ {
+ Usage("Run a diagnostics sub-command (e.g. codegen-preview)").Arguments(x => x.Action)
+ .ValidFlags(x => x.HandlerFlag, x => x.RouteFlag);
+ }
+
+ public override async Task Execute(WolverineDiagnosticsInput input)
+ {
+ switch (input.Action.ToLowerInvariant())
+ {
+ case "codegen-preview":
+ return await RunCodegenPreviewAsync(input);
+
+ default:
+ AnsiConsole.MarkupLine(
+ $"[red]Unknown sub-command '{input.Action}'. Valid sub-commands: codegen-preview[/]");
+ return false;
+ }
+ }
+
+ private static async Task RunCodegenPreviewAsync(WolverineDiagnosticsInput input)
+ {
+ if (input.HandlerFlag.IsEmpty() && input.RouteFlag.IsEmpty())
+ {
+ AnsiConsole.MarkupLine(
+ "[red]codegen-preview requires either --handler or --route \"METHOD /path\".[/]");
+ return false;
+ }
+
+ // Set codegen mode BEFORE building the host so Wolverine applies lightweight startup
+ // (no database connections, no transport connections).
+ DynamicCodeBuilder.WithinCodegenCommand = true;
+
+ try
+ {
+ using var host = input.BuildHost();
+
+ // Starting the host with WithinCodegenCommand=true applies lightweight mode
+ // automatically (stubs transports, disables durability). This is necessary to
+ // ensure HandlerGraph.Compile() runs and HTTP chains are registered.
+ await host.StartAsync();
+
+ var services = host.Services;
+ var serviceVariableSource = services.GetService();
+
+ if (!input.HandlerFlag.IsEmpty())
+ {
+ return PreviewHandlerCode(input.HandlerFlag, services, serviceVariableSource);
+ }
+
+ return PreviewRouteCode(input.RouteFlag, services, serviceVariableSource);
+ }
+ finally
+ {
+ DynamicCodeBuilder.WithinCodegenCommand = false;
+ }
+ }
+
+ private static bool PreviewHandlerCode(
+ string handlerSearch,
+ IServiceProvider services,
+ IServiceVariableSource? serviceVariableSource)
+ {
+ var handlerGraph = services.GetRequiredService();
+ var chains = handlerGraph.AllChains().ToArray();
+
+ var chain = FindHandlerChain(handlerSearch, chains);
+
+ if (chain == null)
+ {
+ AnsiConsole.MarkupLine($"[red]No handler found matching '[bold]{Markup.Escape(handlerSearch)}[/]'.[/]");
+ AnsiConsole.MarkupLine("[grey]Available message handlers:[/]");
+ foreach (var c in chains.Take(30))
+ {
+ AnsiConsole.MarkupLine($" [grey]{Markup.Escape(c.MessageType.FullName ?? c.MessageType.Name)}[/]");
+ }
+
+ if (chains.Length > 30)
+ {
+ AnsiConsole.MarkupLine($" [grey]... and {chains.Length - 30} more[/]");
+ }
+
+ return false;
+ }
+
+ var code = GenerateSingleFileCode(handlerGraph, chain, serviceVariableSource);
+ PrintCodegenResult(
+ $"Handler chain for message: {chain.MessageType.FullName}",
+ chain.Description,
+ code);
+ return true;
+ }
+
+ internal static HandlerChain? FindHandlerChain(string search, HandlerChain[] chains)
+ {
+ // 1. Exact full name match on the message type
+ var chain = chains.FirstOrDefault(c =>
+ string.Equals(c.MessageType.FullName, search, StringComparison.OrdinalIgnoreCase));
+
+ if (chain != null) return chain;
+
+ // 2. Short name match on message type
+ chain = chains.FirstOrDefault(c =>
+ string.Equals(c.MessageType.Name, search, StringComparison.OrdinalIgnoreCase));
+
+ if (chain != null) return chain;
+
+ // 3. Handler class name match (e.g. "CreateOrderHandler")
+ chain = chains.FirstOrDefault(c =>
+ c.Handlers.Any(h =>
+ string.Equals(h.HandlerType.Name, search, StringComparison.OrdinalIgnoreCase)));
+
+ if (chain != null) return chain;
+
+ // 4. Fuzzy contains match — message type full name or handler class name
+ var matches = chains.Where(c =>
+ (c.MessageType.FullName?.Contains(search, StringComparison.OrdinalIgnoreCase) ?? false) ||
+ c.MessageType.Name.Contains(search, StringComparison.OrdinalIgnoreCase) ||
+ c.Handlers.Any(h => h.HandlerType.Name.Contains(search, StringComparison.OrdinalIgnoreCase))
+ ).ToArray();
+
+ if (matches.Length == 1) return matches[0];
+
+ if (matches.Length > 1)
+ {
+ AnsiConsole.MarkupLine($"[yellow]Multiple handlers match '[bold]{Markup.Escape(search)}[/]'. Please be more specific:[/]");
+ foreach (var m in matches)
+ {
+ AnsiConsole.MarkupLine($" [yellow]{Markup.Escape(m.MessageType.FullName ?? m.MessageType.Name)}[/]");
+ }
+ }
+
+ return null;
+ }
+
+ private static bool PreviewRouteCode(
+ string routeInput,
+ IServiceProvider services,
+ IServiceVariableSource? serviceVariableSource)
+ {
+ // Compute the expected file name the HttpChain would generate for this route.
+ // This mirrors the logic in HttpChain.determineFileName() so we can match without
+ // taking a direct compile-time dependency on Wolverine.Http from core Wolverine.
+ var expectedFileName = RouteInputToFileName(routeInput);
+
+ // Search all registered ICodeFileCollection instances (includes HandlerGraph and
+ // any supplemental collections such as HttpGraph added by Wolverine.Http).
+ var allCollections = services.GetServices().ToArray();
+
+ ICodeFileCollection? foundCollection = null;
+ ICodeFile? foundFile = null;
+
+ foreach (var collection in allCollections)
+ {
+ foreach (var file in collection.BuildFiles())
+ {
+ var fileName = file.FileName.Replace(" ", "_");
+ if (string.Equals(fileName, expectedFileName, StringComparison.OrdinalIgnoreCase))
+ {
+ foundCollection = collection;
+ foundFile = file;
+ break;
+ }
+ }
+
+ if (foundFile != null) break;
+ }
+
+ if (foundFile == null)
+ {
+ AnsiConsole.MarkupLine(
+ $"[red]No HTTP endpoint found matching '[bold]{Markup.Escape(routeInput)}[/]' " +
+ $"(expected file name: [bold]{Markup.Escape(expectedFileName)}[/]).[/]");
+ AnsiConsole.MarkupLine("[grey]Available HTTP endpoints (file names):[/]");
+
+ foreach (var collection in allCollections)
+ {
+ foreach (var f in collection.BuildFiles())
+ {
+ // Heuristic: HTTP chain file names start with an HTTP method prefix
+ var fn = f.FileName;
+ if (fn.StartsWith("GET_", StringComparison.OrdinalIgnoreCase) ||
+ fn.StartsWith("POST_", StringComparison.OrdinalIgnoreCase) ||
+ fn.StartsWith("PUT_", StringComparison.OrdinalIgnoreCase) ||
+ fn.StartsWith("DELETE_", StringComparison.OrdinalIgnoreCase) ||
+ fn.StartsWith("PATCH_", StringComparison.OrdinalIgnoreCase))
+ {
+ AnsiConsole.MarkupLine($" [grey]{Markup.Escape(fn)}[/]");
+ }
+ }
+ }
+
+ return false;
+ }
+
+ var code = GenerateSingleFileCode(foundCollection!, foundFile, serviceVariableSource);
+ PrintCodegenResult($"HTTP endpoint: {routeInput}", foundFile.FileName, code);
+ return true;
+ }
+
+ ///
+ /// Converts a route string like "POST /api/orders/{id}" to the file name that HttpChain
+ /// would generate (e.g. "POST_api_orders_id"). Mirrors HttpChain.determineFileName().
+ ///
+ internal static string RouteInputToFileName(string routeInput)
+ {
+ var trimmed = routeInput.Trim();
+ var spaceIndex = trimmed.IndexOf(' ');
+
+ string method;
+ string path;
+
+ if (spaceIndex > 0)
+ {
+ method = trimmed[..spaceIndex].ToUpperInvariant();
+ path = trimmed[(spaceIndex + 1)..];
+ }
+ else
+ {
+ method = string.Empty;
+ path = trimmed;
+ }
+
+ // Mirror HttpChain path processing: strip route constraint suffixes, braces, wildcards, dots
+ var pathParts = path
+ .Replace("{", "")
+ .Replace("}", "")
+ .Replace("*", "")
+ .Replace("?", "")
+ .Replace(".", "_")
+ .Split('/')
+ .Select(segment => segment.Split(':').First());
+
+ var segments = (method.Length > 0 ? new[] { method } : Array.Empty())
+ .Concat(pathParts);
+
+ return string.Join("_", segments).Replace('-', '_').Replace("__", "_").Trim('_');
+ }
+
+ private static string GenerateSingleFileCode(
+ ICodeFileCollection collection,
+ ICodeFile file,
+ IServiceVariableSource? serviceVariableSource)
+ {
+ var generatedAssembly = collection.StartAssembly(collection.Rules);
+ file.AssembleTypes(generatedAssembly);
+
+ // Pass the service variable source only when the collection requires IoC resolution
+ var svs = collection is ICodeFileCollectionWithServices ? serviceVariableSource : null;
+ return generatedAssembly.GenerateCode(svs);
+ }
+
+ private static void PrintCodegenResult(string heading, string description, string code)
+ {
+ AnsiConsole.MarkupLine($"[bold green]{Markup.Escape(heading)}[/]");
+ AnsiConsole.MarkupLine($"[grey]{Markup.Escape(description)}[/]");
+ AnsiConsole.WriteLine();
+ // Print the raw code without markup so it is copy-pasteable
+ Console.WriteLine(code);
+ }
+}
diff --git a/src/Wolverine/Runtime/WolverineRuntime.HostService.cs b/src/Wolverine/Runtime/WolverineRuntime.HostService.cs
index a61059974..a1fe4f44a 100644
--- a/src/Wolverine/Runtime/WolverineRuntime.HostService.cs
+++ b/src/Wolverine/Runtime/WolverineRuntime.HostService.cs
@@ -21,11 +21,36 @@ public partial class WolverineRuntime
private bool _hasStarted;
private Task? _idleAgentCleanupLoop;
+ ///
+ /// Detects whether Wolverine is running in a metadata-only CLI mode (codegen, OpenAPI
+ /// generation via GetDocument.Insider) where persistence and transport connectivity
+ /// are not required. When detected, lightweight startup settings are applied automatically
+ /// so the host can start without needing external databases or message brokers.
+ ///
+ private void applyMetadataOnlyModeIfDetected()
+ {
+ if (Options.LightweightMode) return; // Already applied (e.g., by StartLightweightAsync)
+
+ var isMetadataOnly = DynamicCodeBuilder.WithinCodegenCommand
+ || (Environment.GetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES")
+ ?.Contains("GetDocument", StringComparison.OrdinalIgnoreCase) ?? false);
+
+ if (!isMetadataOnly) return;
+
+ Options.ExternalTransportsAreStubbed = true;
+ Options.Durability.DurabilityAgentEnabled = false;
+ Options.Durability.Mode = DurabilityMode.MediatorOnly;
+ Options.LightweightMode = true;
+ }
+
public async Task StartAsync(CancellationToken cancellationToken)
{
// Make this idempotent because the AddResourceSetupOnStartup() can cause it to bootstrap twice
if (_hasStarted) return;
-
+
+ // Auto-detect codegen and OpenAPI generation tools; suppress persistence/transport init
+ applyMetadataOnlyModeIfDetected();
+
try
{
Logger.LogInformation("Starting Wolverine messaging for application assembly {Assembly}",