From 4fe7b178fd3dbb0c6253869e4d8a55bd1d2343fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:27:35 +0000 Subject: [PATCH 1/3] Initial plan From b6ab6d80719a7fc3461c9319dcb4086d11b928e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:33:01 +0000 Subject: [PATCH 2/3] Fix text output returned despite --output json in detached mode Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> --- DevProxy/Program.cs | 142 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 121 insertions(+), 21 deletions(-) diff --git a/DevProxy/Program.cs b/DevProxy/Program.cs index 78912573..a90a8db6 100644 --- a/DevProxy/Program.cs +++ b/DevProxy/Program.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using DevProxy; +using DevProxy.Abstractions.Utils; using DevProxy.Commands; using DevProxy.Proxy; using DevProxy.State; @@ -10,6 +11,7 @@ using System.Net; using System.Runtime.InteropServices; using System.Text; +using System.Text.Json; // Handle detached mode - spawn a child process and exit // Only applies to root command (starting the proxy), not subcommands @@ -46,12 +48,26 @@ static async Task StartDetachedProcessAsync(string[] args) { + var isJsonOutput = IsJsonOutputRequested(args); + // Check if an instance is already running if (await StateManager.IsInstanceRunningAsync()) { var existingState = await StateManager.LoadStateAsync(); - await Console.Error.WriteLineAsync($"Dev Proxy is already running (PID: {existingState?.Pid})."); - await Console.Error.WriteLineAsync("Use 'devproxy stop' to stop it first."); + if (isJsonOutput) + { + var json = JsonSerializer.Serialize(new + { + error = $"Dev Proxy is already running (PID: {existingState?.Pid}).", + message = "Use 'devproxy stop' to stop it first." + }, ProxyUtils.JsonSerializerOptions); + await Console.Error.WriteLineAsync(json); + } + else + { + await Console.Error.WriteLineAsync($"Dev Proxy is already running (PID: {existingState?.Pid})."); + await Console.Error.WriteLineAsync("Use 'devproxy stop' to stop it first."); + } return 1; } @@ -68,7 +84,18 @@ static async Task StartDetachedProcessAsync(string[] args) var executablePath = Environment.ProcessPath; if (string.IsNullOrEmpty(executablePath)) { - await Console.Error.WriteLineAsync("Could not determine executable path."); + if (isJsonOutput) + { + var json = JsonSerializer.Serialize(new + { + error = "Could not determine executable path." + }, ProxyUtils.JsonSerializerOptions); + await Console.Error.WriteLineAsync(json); + } + else + { + await Console.Error.WriteLineAsync("Could not determine executable path."); + } return 1; } @@ -94,7 +121,18 @@ static async Task StartDetachedProcessAsync(string[] args) var process = Process.Start(startInfo); if (process == null) { - await Console.Error.WriteLineAsync("Failed to start Dev Proxy process."); + if (isJsonOutput) + { + var json = JsonSerializer.Serialize(new + { + error = "Failed to start Dev Proxy process." + }, ProxyUtils.JsonSerializerOptions); + await Console.Error.WriteLineAsync(json); + } + else + { + await Console.Error.WriteLineAsync("Failed to start Dev Proxy process."); + } return 1; } @@ -109,15 +147,28 @@ static async Task StartDetachedProcessAsync(string[] args) var state = await StateManager.LoadStateAsync(); if (state != null) { - await Console.Out.WriteLineAsync("Dev Proxy started in background."); - await Console.Out.WriteLineAsync(); - await Console.Out.WriteLineAsync($" PID: {state.Pid}"); - await Console.Out.WriteLineAsync($" API URL: {state.ApiUrl}"); - await Console.Out.WriteLineAsync($" Log file: {state.LogFile}"); - await Console.Out.WriteLineAsync(); - await Console.Out.WriteLineAsync("Use 'devproxy status' to check status."); - await Console.Out.WriteLineAsync("Use 'devproxy logs' to view logs."); - await Console.Out.WriteLineAsync("Use 'devproxy stop' to stop."); + if (isJsonOutput) + { + var json = JsonSerializer.Serialize(new + { + state.Pid, + state.ApiUrl, + state.LogFile + }, ProxyUtils.JsonSerializerOptions); + await Console.Out.WriteLineAsync(json); + } + else + { + await Console.Out.WriteLineAsync("Dev Proxy started in background."); + await Console.Out.WriteLineAsync(); + await Console.Out.WriteLineAsync($" PID: {state.Pid}"); + await Console.Out.WriteLineAsync($" API URL: {state.ApiUrl}"); + await Console.Out.WriteLineAsync($" Log file: {state.LogFile}"); + await Console.Out.WriteLineAsync(); + await Console.Out.WriteLineAsync("Use 'devproxy status' to check status."); + await Console.Out.WriteLineAsync("Use 'devproxy logs' to view logs."); + await Console.Out.WriteLineAsync("Use 'devproxy stop' to stop."); + } return 0; } @@ -127,30 +178,79 @@ static async Task StartDetachedProcessAsync(string[] args) var errorOutput = await process.StandardError.ReadToEndAsync(); var standardOutput = await process.StandardOutput.ReadToEndAsync(); - await Console.Error.WriteLineAsync("Dev Proxy failed to start."); - if (!string.IsNullOrEmpty(errorOutput)) + if (isJsonOutput) { - await Console.Error.WriteLineAsync(errorOutput); + var json = JsonSerializer.Serialize(new + { + error = "Dev Proxy failed to start.", + errorOutput = string.IsNullOrEmpty(errorOutput) ? null : errorOutput, + standardOutput = string.IsNullOrEmpty(standardOutput) ? null : standardOutput + }, ProxyUtils.JsonSerializerOptions); + await Console.Error.WriteLineAsync(json); } - if (!string.IsNullOrEmpty(standardOutput)) + else { - await Console.Error.WriteLineAsync(standardOutput); + await Console.Error.WriteLineAsync("Dev Proxy failed to start."); + if (!string.IsNullOrEmpty(errorOutput)) + { + await Console.Error.WriteLineAsync(errorOutput); + } + if (!string.IsNullOrEmpty(standardOutput)) + { + await Console.Error.WriteLineAsync(standardOutput); + } } return process.ExitCode; } } - await Console.Error.WriteLineAsync("Timeout waiting for Dev Proxy to start."); - await Console.Error.WriteLineAsync($"Check the log folder: {StateManager.GetLogsFolder()}"); + if (isJsonOutput) + { + var json = JsonSerializer.Serialize(new + { + error = "Timeout waiting for Dev Proxy to start.", + logFolder = StateManager.GetLogsFolder() + }, ProxyUtils.JsonSerializerOptions); + await Console.Error.WriteLineAsync(json); + } + else + { + await Console.Error.WriteLineAsync("Timeout waiting for Dev Proxy to start."); + await Console.Error.WriteLineAsync($"Check the log folder: {StateManager.GetLogsFolder()}"); + } return 1; } catch (Exception ex) { - await Console.Error.WriteLineAsync($"Failed to start Dev Proxy: {ex.Message}"); + if (isJsonOutput) + { + var json = JsonSerializer.Serialize(new + { + error = $"Failed to start Dev Proxy: {ex.Message}" + }, ProxyUtils.JsonSerializerOptions); + await Console.Error.WriteLineAsync(json); + } + else + { + await Console.Error.WriteLineAsync($"Failed to start Dev Proxy: {ex.Message}"); + } return 1; } } +static bool IsJsonOutputRequested(string[] args) +{ + for (var i = 0; i < args.Length; i++) + { + if (string.Equals(args[i], DevProxyCommand.OutputOptionName, StringComparison.OrdinalIgnoreCase) && + i + 1 < args.Length) + { + return string.Equals(args[i + 1], "json", StringComparison.OrdinalIgnoreCase); + } + } + return false; +} + static string EscapeArgument(string arg) { // Simple escaping for command line arguments From a65e72517c56c9c3f4c17e3b1ee2861633f67b9e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:00:58 +0000 Subject: [PATCH 3/3] Use consistent JSONL envelope format for --output json in detached mode Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> --- DevProxy/Program.cs | 105 ++++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/DevProxy/Program.cs b/DevProxy/Program.cs index a90a8db6..b7e8ca9e 100644 --- a/DevProxy/Program.cs +++ b/DevProxy/Program.cs @@ -3,15 +3,16 @@ // See the LICENSE file in the project root for more information. using DevProxy; -using DevProxy.Abstractions.Utils; using DevProxy.Commands; using DevProxy.Proxy; using DevProxy.State; using System.Diagnostics; +using System.Globalization; using System.Net; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; // Handle detached mode - spawn a child process and exit // Only applies to root command (starting the proxy), not subcommands @@ -56,12 +57,8 @@ static async Task StartDetachedProcessAsync(string[] args) var existingState = await StateManager.LoadStateAsync(); if (isJsonOutput) { - var json = JsonSerializer.Serialize(new - { - error = $"Dev Proxy is already running (PID: {existingState?.Pid}).", - message = "Use 'devproxy stop' to stop it first." - }, ProxyUtils.JsonSerializerOptions); - await Console.Error.WriteLineAsync(json); + await Console.Error.WriteLineAsync( + FormatJsonLogEntry("error", $"Dev Proxy is already running (PID: {existingState?.Pid}). Use 'devproxy stop' to stop it first.")); } else { @@ -86,11 +83,8 @@ static async Task StartDetachedProcessAsync(string[] args) { if (isJsonOutput) { - var json = JsonSerializer.Serialize(new - { - error = "Could not determine executable path." - }, ProxyUtils.JsonSerializerOptions); - await Console.Error.WriteLineAsync(json); + await Console.Error.WriteLineAsync( + FormatJsonLogEntry("error", "Could not determine executable path.")); } else { @@ -123,11 +117,8 @@ static async Task StartDetachedProcessAsync(string[] args) { if (isJsonOutput) { - var json = JsonSerializer.Serialize(new - { - error = "Failed to start Dev Proxy process." - }, ProxyUtils.JsonSerializerOptions); - await Console.Error.WriteLineAsync(json); + await Console.Error.WriteLineAsync( + FormatJsonLogEntry("error", "Failed to start Dev Proxy process.")); } else { @@ -149,13 +140,12 @@ static async Task StartDetachedProcessAsync(string[] args) { if (isJsonOutput) { - var json = JsonSerializer.Serialize(new + await Console.Out.WriteLineAsync(FormatJsonResultEntry(new { state.Pid, state.ApiUrl, state.LogFile - }, ProxyUtils.JsonSerializerOptions); - await Console.Out.WriteLineAsync(json); + })); } else { @@ -180,13 +170,16 @@ static async Task StartDetachedProcessAsync(string[] args) if (isJsonOutput) { - var json = JsonSerializer.Serialize(new + var message = "Dev Proxy failed to start."; + if (!string.IsNullOrEmpty(errorOutput)) + { + message += $" {errorOutput.Trim()}"; + } + if (!string.IsNullOrEmpty(standardOutput)) { - error = "Dev Proxy failed to start.", - errorOutput = string.IsNullOrEmpty(errorOutput) ? null : errorOutput, - standardOutput = string.IsNullOrEmpty(standardOutput) ? null : standardOutput - }, ProxyUtils.JsonSerializerOptions); - await Console.Error.WriteLineAsync(json); + message += $" {standardOutput.Trim()}"; + } + await Console.Error.WriteLineAsync(FormatJsonLogEntry("error", message)); } else { @@ -206,12 +199,8 @@ static async Task StartDetachedProcessAsync(string[] args) if (isJsonOutput) { - var json = JsonSerializer.Serialize(new - { - error = "Timeout waiting for Dev Proxy to start.", - logFolder = StateManager.GetLogsFolder() - }, ProxyUtils.JsonSerializerOptions); - await Console.Error.WriteLineAsync(json); + await Console.Error.WriteLineAsync( + FormatJsonLogEntry("error", $"Timeout waiting for Dev Proxy to start. Check the log folder: {StateManager.GetLogsFolder()}")); } else { @@ -224,11 +213,8 @@ static async Task StartDetachedProcessAsync(string[] args) { if (isJsonOutput) { - var json = JsonSerializer.Serialize(new - { - error = $"Failed to start Dev Proxy: {ex.Message}" - }, ProxyUtils.JsonSerializerOptions); - await Console.Error.WriteLineAsync(json); + await Console.Error.WriteLineAsync( + FormatJsonLogEntry("error", $"Failed to start Dev Proxy: {ex.Message}")); } else { @@ -238,6 +224,51 @@ static async Task StartDetachedProcessAsync(string[] args) } } +/// +/// Formats a result entry matching the JSONL envelope produced by JsonConsoleFormatter: +/// {"type":"result","data":{...},"timestamp":"..."} +/// +static string FormatJsonResultEntry(object data) +{ +#pragma warning disable CA1869 // Called at most once per process lifetime + var jsonlOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; +#pragma warning restore CA1869 + var entry = new + { + type = "result", + data = JsonSerializer.SerializeToElement(data, jsonlOptions), + timestamp = DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture) + }; + return JsonSerializer.Serialize(entry, jsonlOptions); +} + +/// +/// Formats a log entry matching the JSONL envelope produced by JsonConsoleFormatter: +/// {"type":"log","level":"...","message":"...","timestamp":"..."} +/// +static string FormatJsonLogEntry(string level, string message) +{ +#pragma warning disable CA1869 // Called at most once per process lifetime + var jsonlOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; +#pragma warning restore CA1869 + var entry = new + { + type = "log", + level, + message, + timestamp = DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture) + }; + return JsonSerializer.Serialize(entry, jsonlOptions); +} + static bool IsJsonOutputRequested(string[] args) { for (var i = 0; i < args.Length; i++)