From f43a29869c2080c9ad6e19c41d0b214fc3ba59c9 Mon Sep 17 00:00:00 2001 From: waldekmastykarz Date: Sat, 11 Apr 2026 17:53:38 +0200 Subject: [PATCH 1/2] Changes config get to download configs to user data folder instead of installation folder. Closes #1564 --- DevProxy.Abstractions/Utils/ProxyUtils.cs | 19 +++++++++++++++ DevProxy/Commands/ConfigCommand.cs | 28 ++++++++--------------- DevProxy/Commands/DevProxyCommand.cs | 2 +- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/DevProxy.Abstractions/Utils/ProxyUtils.cs b/DevProxy.Abstractions/Utils/ProxyUtils.cs index d07367e7..b5799481 100644 --- a/DevProxy.Abstractions/Utils/ProxyUtils.cs +++ b/DevProxy.Abstractions/Utils/ProxyUtils.cs @@ -10,6 +10,7 @@ using System.Collections.ObjectModel; using System.Reflection; using System.Text.Encodings.Web; +using System.Runtime.InteropServices; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; @@ -42,6 +43,23 @@ public static class ProxyUtils // doesn't end with a path separator public static string? AppFolder => Path.GetDirectoryName(AppContext.BaseDirectory); + + /// + /// Gets the path to the user data folder for Dev Proxy. + /// On macOS: ~/Library/Application Support/dev-proxy/ + /// On Linux: ~/.config/dev-proxy/ (or $XDG_CONFIG_HOME/dev-proxy/) + /// On Windows: %LocalAppData%\dev-proxy\ + /// + public static string DataFolder + { + get + { + var basePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + return Path.Combine(basePath, "dev-proxy"); + } + } public static JsonSerializerOptions JsonSerializerOptions { get; } = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, @@ -175,6 +193,7 @@ public static string ReplacePathTokens(string? path) return path ?? string.Empty; } + path = path.Replace("~dataFolder", DataFolder, StringComparison.OrdinalIgnoreCase); return path.Replace("~appFolder", AppFolder, StringComparison.OrdinalIgnoreCase); } diff --git a/DevProxy/Commands/ConfigCommand.cs b/DevProxy/Commands/ConfigCommand.cs index c502bc8f..4d46872c 100644 --- a/DevProxy/Commands/ConfigCommand.cs +++ b/DevProxy/Commands/ConfigCommand.cs @@ -215,17 +215,9 @@ private async Task DownloadConfigAsync(string configId, OutputFormat outputForma { try { - var appFolder = ProxyUtils.AppFolder; - if (string.IsNullOrEmpty(appFolder) || !Directory.Exists(appFolder)) - { - if (outputFormat == OutputFormat.Text) - { - _logger.LogError("App folder {AppFolder} not found", appFolder); - } - return; - } + var dataFolder = ProxyUtils.DataFolder; - var configFolderPath = Path.Combine(appFolder, "config"); + var configFolderPath = Path.Combine(dataFolder, "configs"); _logger.LogDebug("Checking if config folder {ConfigFolderPath} exists...", configFolderPath); if (!Directory.Exists(configFolderPath)) { @@ -235,7 +227,7 @@ private async Task DownloadConfigAsync(string configId, OutputFormat outputForma } _logger.LogDebug("Getting target folder path for config {ConfigId}...", configId); - var targetFolderPath = GetTargetFolderPath(appFolder, configId); + var targetFolderPath = GetTargetFolderPath(dataFolder, configId); _logger.LogDebug("Creating target folder {TargetFolderPath}...", targetFolderPath); _ = Directory.CreateDirectory(targetFolderPath); @@ -287,7 +279,7 @@ private async Task DownloadConfigAsync(string configId, OutputFormat outputForma { if (_logger.IsEnabled(LogLevel.Information)) { - _logger.LogInformation(" devproxy --config-file \"{ConfigFile}\"", configFile.Replace(appFolder, "~appFolder", StringComparison.OrdinalIgnoreCase)); + _logger.LogInformation(" devproxy --config-file \"{ConfigFile}\"", configFile.Replace(dataFolder, "~dataFolder", StringComparison.OrdinalIgnoreCase)); } } } @@ -298,7 +290,7 @@ private async Task DownloadConfigAsync(string configId, OutputFormat outputForma { if (_logger.IsEnabled(LogLevel.Information)) { - _logger.LogInformation(" devproxy --mock-file \"{MockFile}\"", mockFile.Replace(appFolder, "~appFolder", StringComparison.OrdinalIgnoreCase)); + _logger.LogInformation(" devproxy --mock-file \"{MockFile}\"", mockFile.Replace(dataFolder, "~dataFolder", StringComparison.OrdinalIgnoreCase)); } } } @@ -332,13 +324,13 @@ private ProxyConfigInfo GetConfigInfo(string configFolder) var configInfo = new ProxyConfigInfo(); _logger.LogDebug("Getting list of config files in {ConfigFolder}...", configFolder); - + // Get both JSON and YAML files var jsonFiles = Directory.GetFiles(configFolder, "*.json"); var yamlFiles = Directory.GetFiles(configFolder, "*.yaml"); var ymlFiles = Directory.GetFiles(configFolder, "*.yml"); var allConfigFiles = jsonFiles.Concat(yamlFiles).Concat(ymlFiles).ToArray(); - + if (allConfigFiles.Length == 0) { _logger.LogDebug("No config files found"); @@ -350,7 +342,7 @@ private ProxyConfigInfo GetConfigInfo(string configFolder) _logger.LogDebug("Reading file {ConfigFile}...", configFile); var fileContents = File.ReadAllText(configFile); - + // Check for plugins marker (case-insensitive) // For JSON: "plugins": // For YAML: plugins: @@ -561,9 +553,9 @@ private string GetTargetFileName(string name) } } - private static string GetTargetFolderPath(string appFolder, string configId) + private static string GetTargetFolderPath(string dataFolder, string configId) { - var baseFolder = Path.Combine(appFolder, "config", configId); + var baseFolder = Path.Combine(dataFolder, "configs", configId); var newFolder = baseFolder; var i = 1; while (Directory.Exists(newFolder)) diff --git a/DevProxy/Commands/DevProxyCommand.cs b/DevProxy/Commands/DevProxyCommand.cs index 9f124bd4..6bc5f71d 100644 --- a/DevProxy/Commands/DevProxyCommand.cs +++ b/DevProxy/Commands/DevProxyCommand.cs @@ -33,7 +33,7 @@ sealed class DevProxyCommand : RootCommand internal static readonly Option ConfigFileOption = new(ConfigFileOptionName, "-c") { HelpName = "config-file", - Description = "Path to config file. If not specified, Dev Proxy searches for devproxyrc.jsonc or devproxyrc.json in the current directory, then in a .devproxy/ directory, then under the ~appFolder location. Supports ~appFolder token." + Description = "Path to config file. If not specified, Dev Proxy searches for devproxyrc.jsonc or devproxyrc.json in the current directory, then in a .devproxy/ directory, then under the ~appFolder location. Supports ~appFolder and ~dataFolder tokens." }; internal const string NoFirstRunOptionName = "--no-first-run"; internal const string NoWatchOptionName = "--no-watch"; From 531f1fecb07db07679b7388ac6e0595a693805a7 Mon Sep 17 00:00:00 2001 From: waldekmastykarz Date: Sat, 11 Apr 2026 18:27:54 +0200 Subject: [PATCH 2/2] Refactor logging setup and remove unused warnings parameter in ValidatePlugins method --- DevProxy/Commands/ConfigCommand.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/DevProxy/Commands/ConfigCommand.cs b/DevProxy/Commands/ConfigCommand.cs index 4d46872c..5ead2b1e 100644 --- a/DevProxy/Commands/ConfigCommand.cs +++ b/DevProxy/Commands/ConfigCommand.cs @@ -106,7 +106,7 @@ internal static async Task RunValidateStandaloneAsync(string[] args) using var loggerFactory = LoggerFactory.Create(builder => { - builder + _ = builder .SetMinimumLevel(LogLevel.Information) .AddConsole(consoleOptions => { @@ -693,7 +693,7 @@ private static async Task ValidateConfigCoreAsync( if (configDoc.RootElement.TryGetProperty("plugins", out var pluginsElement) && pluginsElement.ValueKind == JsonValueKind.Array) { - ValidatePlugins(pluginsElement, configFileDirectory, errors, warnings, pluginNames); + ValidatePlugins(pluginsElement, configFileDirectory, errors, pluginNames); } else { @@ -729,7 +729,6 @@ private static void ValidatePlugins( JsonElement pluginsElement, string configFileDirectory, List errors, - List warnings, List pluginNames) { var hasEnabledPlugins = false;