Skip to content
3 changes: 3 additions & 0 deletions DevProxy/Commands/DevProxyConfigOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public string? ConfigFile
}

public int? ApiPort => _parseResult?.GetValueOrDefault<int?>(DevProxyCommand.ApiPortOptionName);
public bool? AsSystemProxy => _parseResult?.GetValueOrDefault<bool?>(DevProxyCommand.AsSystemProxyOptionName);
public int? Port => _parseResult?.GetValueOrDefault<int?>(DevProxyCommand.PortOptionName);
public bool Discover => _parseResult?.GetValueOrDefault<bool?>(DevProxyCommand.DiscoverOptionName) ?? false;
public string? IPAddress => _parseResult?.GetValueOrDefault<string?>(DevProxyCommand.IpAddressOptionName);
Expand Down Expand Up @@ -138,6 +139,7 @@ public DevProxyConfigOptions()
};

var apiPortOption = new Option<int?>(DevProxyCommand.ApiPortOptionName);
var asSystemProxyOption = new Option<bool?>(DevProxyCommand.AsSystemProxyOptionName);
var portOption = new Option<int?>(DevProxyCommand.PortOptionName, "-p");

var discoverOption = new Option<bool>(DevProxyCommand.DiscoverOptionName, "--discover")
Expand All @@ -153,6 +155,7 @@ public DevProxyConfigOptions()
var options = new List<Option>
{
apiPortOption,
asSystemProxyOption,
ipAddressOption,
configFileOption,
portOption,
Expand Down
29 changes: 24 additions & 5 deletions DevProxy/Commands/LogsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ internal sealed class LogsCommand : Command
HelpName = "time"
};

private readonly Option<int?> _pidOption = new("--pid")
{
Description = "Show logs from a specific Dev Proxy instance by PID"
};

public LogsCommand() : base("logs", "Show logs from running Dev Proxy instance")
{
Add(_followOption);
Add(_linesOption);
Add(_sinceOption);
Add(_pidOption);

SetAction(RunAsync);
}
Expand All @@ -42,13 +48,26 @@ private async Task<int> RunAsync(ParseResult parseResult, CancellationToken canc
var follow = parseResult.GetValue(_followOption);
var lines = parseResult.GetValue(_linesOption);
var since = parseResult.GetValue(_sinceOption);
var pid = parseResult.GetValue(_pidOption);

var state = await StateManager.LoadStateAsync(cancellationToken);

if (state == null)
ProxyInstanceState? state;
if (pid is not null)
{
Console.WriteLine("Dev Proxy is not running.");
return 1;
state = await StateManager.LoadStateByPidAsync(pid.Value, cancellationToken);
if (state is null)
{
Console.WriteLine($"No running Dev Proxy instance with PID {pid.Value}.");
return 1;
}
}
else
{
state = await StateManager.LoadStateAsync(cancellationToken);
if (state is null)
{
Console.WriteLine("Dev Proxy is not running.");
return 1;
}
}

var logFile = state.LogFile;
Expand Down
69 changes: 52 additions & 17 deletions DevProxy/Commands/StatusCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,56 @@ namespace DevProxy.Commands;

internal sealed class StatusCommand : Command
{
public StatusCommand() : base("status", "Show status of running Dev Proxy instance")
private readonly Option<int?> _pidOption = new("--pid")
{
Description = "Show status of a specific Dev Proxy instance by PID"
};

public StatusCommand() : base("status", "Show status of running Dev Proxy instances")
{
Add(_pidOption);
SetAction(RunAsync);
}

private async Task<int> RunAsync(ParseResult parseResult, CancellationToken cancellationToken)
{
var state = await StateManager.LoadStateAsync(cancellationToken);
var pid = parseResult.GetValue(_pidOption);

if (state == null)
if (pid is not null)
{
var state = await StateManager.LoadStateByPidAsync(pid.Value, cancellationToken);
if (state is null)
{
Console.WriteLine($"No running Dev Proxy instance with PID {pid.Value}.");
return 1;
}

await PrintInstanceStatusAsync(state, cancellationToken);
return 0;
}

var states = await StateManager.LoadAllStatesAsync(cancellationToken);
if (states.Count == 0)
{
Console.WriteLine("Dev Proxy is not running.");
return 1;
}

for (var i = 0; i < states.Count; i++)
{
if (i > 0)
{
Console.WriteLine();
}

await PrintInstanceStatusAsync(states[i], cancellationToken);
}

return 0;
}

private static async Task PrintInstanceStatusAsync(ProxyInstanceState state, CancellationToken cancellationToken)
{
// Try to get live status from the API
try
{
Expand All @@ -37,18 +72,19 @@ private async Task<int> RunAsync(ParseResult parseResult, CancellationToken canc

Console.WriteLine("Dev Proxy is running.");
Console.WriteLine();
Console.WriteLine($" PID: {state.Pid}");
Console.WriteLine($" API URL: {state.ApiUrl}");
Console.WriteLine($" Port: {state.Port}");
Console.WriteLine($" Recording: {(proxyInfo?.Recording == true ? "Yes" : "No")}");
Console.WriteLine($" PID: {state.Pid}");
Console.WriteLine($" API URL: {state.ApiUrl}");
Console.WriteLine($" Port: {state.Port}");
Console.WriteLine($" System proxy: {(state.AsSystemProxy ? "Yes" : "No")}");
Console.WriteLine($" Recording: {(proxyInfo?.Recording == true ? "Yes" : "No")}");
if (!string.IsNullOrEmpty(state.ConfigFile))
{
Console.WriteLine($" Config: {state.ConfigFile}");
Console.WriteLine($" Config: {state.ConfigFile}");
}
Console.WriteLine($" Log file: {state.LogFile}");
Console.WriteLine($" Started: {state.StartedAt.LocalDateTime:g}");
Console.WriteLine($" Log file: {state.LogFile}");
Console.WriteLine($" Started: {state.StartedAt.LocalDateTime:g}");

return 0;
return;
}
}
catch (HttpRequestException)
Expand All @@ -63,12 +99,11 @@ private async Task<int> RunAsync(ParseResult parseResult, CancellationToken canc
// Fall back to state file info
Console.WriteLine("Dev Proxy appears to be running (API not responding).");
Console.WriteLine();
Console.WriteLine($" PID: {state.Pid}");
Console.WriteLine($" API URL: {state.ApiUrl}");
Console.WriteLine($" Log file: {state.LogFile}");
Console.WriteLine($" Started: {state.StartedAt.LocalDateTime:g}");

return 0;
Console.WriteLine($" PID: {state.Pid}");
Console.WriteLine($" API URL: {state.ApiUrl}");
Console.WriteLine($" System proxy: {(state.AsSystemProxy ? "Yes" : "No")}");
Console.WriteLine($" Log file: {state.LogFile}");
Console.WriteLine($" Started: {state.StartedAt.LocalDateTime:g}");
}

private sealed class ProxyStatusInfo
Expand Down
64 changes: 49 additions & 15 deletions DevProxy/Commands/StopCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,57 @@ internal sealed class StopCommand : Command
Description = "Force stop the proxy by killing the process"
};

public StopCommand() : base("stop", "Stop running Dev Proxy instance")
private readonly Option<int?> _pidOption = new("--pid")
{
Description = "Stop a specific Dev Proxy instance by PID"
};

public StopCommand() : base("stop", "Stop running Dev Proxy instances")
{
Add(_forceOption);
Add(_pidOption);
SetAction(RunAsync);
}

private async Task<int> RunAsync(ParseResult parseResult, CancellationToken cancellationToken)
{
var force = parseResult.GetValue(_forceOption);
var state = await StateManager.LoadStateAsync(cancellationToken);
var pid = parseResult.GetValue(_pidOption);

if (pid is not null)
{
var state = await StateManager.LoadStateByPidAsync(pid.Value, cancellationToken);
if (state is null)
{
Console.WriteLine($"No running Dev Proxy instance with PID {pid.Value}.");
return 1;
}

return await StopInstanceAsync(state, force, cancellationToken);
}

if (state == null)
var states = await StateManager.LoadAllStatesAsync(cancellationToken);
if (states.Count == 0)
{
Console.WriteLine("Dev Proxy is not running.");
return 1;
}

var exitCode = 0;
foreach (var state in states)
{
var result = await StopInstanceAsync(state, force, cancellationToken);
if (result != 0)
{
exitCode = result;
}
}

return exitCode;
}

private static async Task<int> StopInstanceAsync(ProxyInstanceState state, bool force, CancellationToken cancellationToken)
{
if (force)
{
return await ForceStopAsync(state, cancellationToken);
Expand All @@ -45,7 +79,7 @@ private async Task<int> RunAsync(ParseResult parseResult, CancellationToken canc

if (response.IsSuccessStatusCode || response.StatusCode == System.Net.HttpStatusCode.Accepted)
{
Console.WriteLine("Stopping Dev Proxy...");
Console.WriteLine($"Stopping Dev Proxy (PID: {state.Pid})...");

// Wait for process to exit
var stopwatch = Stopwatch.StartNew();
Expand Down Expand Up @@ -82,30 +116,30 @@ private async Task<int> RunAsync(ParseResult parseResult, CancellationToken canc

if (!exited)
{
Console.WriteLine("Dev Proxy did not stop in time.");
Console.WriteLine("Use --force to forcefully terminate the process.");
Console.WriteLine($"Dev Proxy (PID: {state.Pid}) did not stop in time.");
Console.WriteLine($"Use --force --pid {state.Pid} to forcefully terminate the process.");
return 1;
}

await StateManager.DeleteStateAsync(cancellationToken);
Console.WriteLine("Dev Proxy stopped.");
await StateManager.DeleteStateAsync(state.Pid, cancellationToken);
Console.WriteLine($"Dev Proxy (PID: {state.Pid}) stopped.");
return 0;
}

Console.WriteLine($"Failed to stop Dev Proxy: {response.StatusCode}");
Console.WriteLine("Use --force to forcefully terminate the process.");
Console.WriteLine($"Failed to stop Dev Proxy (PID: {state.Pid}): {response.StatusCode}");
Console.WriteLine($"Use --force --pid {state.Pid} to forcefully terminate the process.");
return 1;
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Failed to connect to Dev Proxy API: {ex.Message}");
Console.WriteLine("Use --force to forcefully terminate the process.");
Console.WriteLine($"Failed to connect to Dev Proxy API (PID: {state.Pid}): {ex.Message}");
Console.WriteLine($"Use --force --pid {state.Pid} to forcefully terminate the process.");
return 1;
}
catch (TaskCanceledException)
{
Console.WriteLine("Timeout waiting for Dev Proxy to respond.");
Console.WriteLine("Use --force to forcefully terminate the process.");
Console.WriteLine($"Timeout waiting for Dev Proxy (PID: {state.Pid}) to respond.");
Console.WriteLine($"Use --force --pid {state.Pid} to forcefully terminate the process.");
return 1;
}
}
Expand Down Expand Up @@ -135,7 +169,7 @@ private static async Task<int> ForceStopAsync(ProxyInstanceState state, Cancella
return 1;
}

await StateManager.DeleteStateAsync(cancellationToken);
await StateManager.DeleteStateAsync(state.Pid, cancellationToken);
return 0;
}

Expand Down
57 changes: 32 additions & 25 deletions DevProxy/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,42 @@

static async Task<int> StartDetachedProcessAsync(string[] args)
{
// Check if an instance is already running
if (await StateManager.IsInstanceRunningAsync())
// Check if an instance is already running as system proxy
var systemProxyInstance = await StateManager.FindSystemProxyInstanceAsync();
if (systemProxyInstance is not null)
{
var existingState = await StateManager.LoadStateAsync();
await Console.Error.WriteLineAsync($"Dev Proxy is already running (PID: {existingState?.Pid}).");
await Console.Error.WriteLineAsync($"Dev Proxy is already running as system proxy (PID: {systemProxyInstance.Pid}).");
await Console.Error.WriteLineAsync("Use 'devproxy stop' to stop it first.");
return 1;
}

// Check for port conflicts with existing instances
var existingStates = await StateManager.LoadAllStatesAsync();
if (existingStates.Count > 0)
{
var earlyOptions = new DevProxyConfigOptions();
earlyOptions.ParseOptions(args);
var requestedPort = earlyOptions.Port ?? 8000;
var requestedApiPort = earlyOptions.ApiPort ?? 8897;

foreach (var existing in existingStates)
{
if (requestedPort != 0 && existing.Port == requestedPort)
{
await Console.Error.WriteLineAsync($"Port {requestedPort} is already in use by Dev Proxy instance (PID: {existing.Pid}).");
await Console.Error.WriteLineAsync("Use a different --port or stop the existing instance first.");
return 1;
}

if (requestedApiPort != 0 && existing.ApiUrl.EndsWith($":{requestedApiPort}", StringComparison.Ordinal))
{
await Console.Error.WriteLineAsync($"API port {requestedApiPort} is already in use by Dev Proxy instance (PID: {existing.Pid}).");
await Console.Error.WriteLineAsync("Use a different --api-port or stop the existing instance first.");
return 1;
}
}
}

// Clean up old log files
StateManager.CleanupOldLogs();

Expand Down Expand Up @@ -106,7 +133,7 @@ static async Task<int> StartDetachedProcessAsync(string[] args)
{
await Task.Delay(200);

var state = await StateManager.LoadStateAsync();
var state = await StateManager.LoadStateByPidAsync(process.Id);
if (state != null)
{
await Console.Out.WriteLineAsync("Dev Proxy started in background.");
Expand Down Expand Up @@ -199,26 +226,6 @@ static async Task<int> RunProxyAsync(string[] args, DevProxyConfigOptions option
var app = BuildApplication(options);
try
{
// If running as daemon, save state so other commands can find us
if (DevProxyCommand.IsInternalDaemon)
{
var ipAddress = options.IPAddress ?? app.Configuration.GetValue("ipAddress", "127.0.0.1") ?? "127.0.0.1";
var apiPort = options.ApiPort ?? app.Configuration.GetValue("apiPort", 8897);
var port = options.Port ?? app.Configuration.GetValue("port", 8000);

var state = new ProxyInstanceState
{
Pid = Environment.ProcessId,
ApiUrl = $"http://{(ipAddress is "0.0.0.0" or "::" ? "127.0.0.1" : ipAddress)}:{apiPort}",
LogFile = DevProxyCommand.DetachedLogFilePath,
StartedAt = DateTimeOffset.UtcNow,
ConfigFile = options.ConfigFile,
Port = port
};

await StateManager.SaveStateAsync(state);
}

var devProxyCommand = app.Services.GetRequiredService<DevProxyCommand>();
return await devProxyCommand.InvokeAsync(args, app);
}
Expand Down
Loading
Loading