From 9c65ad28973c6a169926351c6bc5ac4a410521d2 Mon Sep 17 00:00:00 2001 From: Waldek Mastykarz Date: Wed, 21 Oct 2026 16:03:22 +0200 Subject: [PATCH] Make env/--env set environment variables for the Dev Proxy process. Closes #1953 Values from the config file's `env` object and `--env` are now set as process environment variables right after the config file is loaded, before plugins are created. `--env` values take precedence. They are still used to replace {name} placeholders in API spec files for the MinimalPermissions plugins. Adds `env` to rc.schema.json and rejects `--env` entries with an empty name. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 846278cd-1ffb-4a4b-a0df-57d766209c4b --- DevProxy.Tests/EnvOptionTests.cs | 72 ++++++++++++++++++++++ DevProxy/Commands/DevProxyCommand.cs | 4 +- DevProxy/Commands/DevProxyConfigOptions.cs | 30 +++++++++ DevProxy/Program.cs | 1 + DevProxy/Proxy/ProxyConfiguration.cs | 18 ++++++ schemas/v4.0.0/rc.schema.json | 11 ++++ 6 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 DevProxy.Tests/EnvOptionTests.cs diff --git a/DevProxy.Tests/EnvOptionTests.cs b/DevProxy.Tests/EnvOptionTests.cs new file mode 100644 index 00000000..6b04c445 --- /dev/null +++ b/DevProxy.Tests/EnvOptionTests.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using DevProxy.Commands; +using DevProxy.Proxy; +using Microsoft.Extensions.Configuration; + +using Xunit; + +namespace DevProxy.Tests; + +public class EnvOptionTests +{ + [Fact] + public void Env_ParsesCommandLineValues() + { + var options = new DevProxyConfigOptions(); + options.ParseOptions(["--env", "FOO=bar", "-e", "URL=https://x?a=b"]); + + var env = options.Env; + + Assert.NotNull(env); + Assert.Equal("bar", env["FOO"]); + Assert.Equal("https://x?a=b", env["URL"]); + } + + [Fact] + public void Env_NotSpecified_ReturnsNull() + { + var options = new DevProxyConfigOptions(); + options.ParseOptions(["--port", "0"]); + + Assert.Null(options.Env); + } + + [Fact] + public void SetEnvironmentVariables_SetsConfigAndCommandLineValues_CommandLineWins() + { + var prefix = $"DEVPROXY_TEST_{Guid.NewGuid():N}_"; + var fromConfig = prefix + "CONFIG"; + var overridden = prefix + "OVERRIDDEN"; + var fromCli = prefix + "CLI"; + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + [$"env:{fromConfig}"] = "config", + [$"env:{overridden}"] = "config", + }) + .Build(); + + try + { + ProxyConfiguration.SetEnvironmentVariables(configuration, new Dictionary + { + [overridden] = "cli", + [fromCli] = "cli", + }); + + Assert.Equal("config", Environment.GetEnvironmentVariable(fromConfig)); + Assert.Equal("cli", Environment.GetEnvironmentVariable(overridden)); + Assert.Equal("cli", Environment.GetEnvironmentVariable(fromCli)); + } + finally + { + Environment.SetEnvironmentVariable(fromConfig, null); + Environment.SetEnvironmentVariable(overridden, null); + Environment.SetEnvironmentVariable(fromCli, null); + } + } +} diff --git a/DevProxy/Commands/DevProxyCommand.cs b/DevProxy/Commands/DevProxyCommand.cs index dfc9b763..b6a3836e 100644 --- a/DevProxy/Commands/DevProxyCommand.cs +++ b/DevProxy/Commands/DevProxyCommand.cs @@ -527,7 +527,7 @@ private void ConfigureCommand() { AllowMultipleArgumentsPerToken = true, Arity = ArgumentArity.ZeroOrMore, - Description = "Variables to set for the Dev Proxy process", + Description = "Environment variables to set for the Dev Proxy process, in name=value format. Also used to replace {name} placeholders in API spec files used by the MinimalPermissions plugins", HelpName = "env", }; envOption.Validators.Add(input => @@ -544,7 +544,7 @@ private void ConfigureCommand() { // Split on first '=' only var parts = envVar.Split('=', 2); - if (parts.Length != 2) + if (parts.Length != 2 || parts[0].Length == 0) { input.AddError($"Invalid environment variable format: '{envVar}'. Expected format is 'name=value'."); return; diff --git a/DevProxy/Commands/DevProxyConfigOptions.cs b/DevProxy/Commands/DevProxyConfigOptions.cs index 11173475..4296b14c 100644 --- a/DevProxy/Commands/DevProxyConfigOptions.cs +++ b/DevProxy/Commands/DevProxyConfigOptions.cs @@ -28,6 +28,29 @@ public string? ConfigFile public OutputFormat? Output => _parseResult?.GetValueOrDefault(DevProxyCommand.OutputOptionName); public LogLevel? LogLevel => _parseResult?.GetValueOrDefault(DevProxyCommand.LogLevelOptionName); + public Dictionary? Env => ParseEnv(_parseResult?.GetValueOrDefault(DevProxyCommand.EnvOptionName)); + + internal static Dictionary? ParseEnv(string[]? env) + { + if (env is null || env.Length == 0) + { + return null; + } + + var result = new Dictionary(); + foreach (var e in env) + { + // Split on first '=' only + var parts = e.Split('=', 2); + if (parts.Length == 2 && parts[0].Length > 0) + { + result[parts[0]] = parts[1]; + } + } + + return result; + } + public List? UrlsToWatch { get @@ -154,6 +177,12 @@ public DevProxyConfigOptions() Arity = ArgumentArity.Zero }; + var envOption = new Option(DevProxyCommand.EnvOptionName, "-e") + { + AllowMultipleArgumentsPerToken = true, + Arity = ArgumentArity.ZeroOrMore + }; + var options = new List