Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Dev Proxy is a cross-platform .NET API simulator and proxy for testing how appli
## Critical (Non-Obvious)

- **Proxy engine:** Built on [ASP.NET Core Kestrel](https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel) (a custom forward-proxy + MITM engine; see `DevProxy.Proxy.Kestrel`)
- **Config tokens:** Paths in config files can use `~appFolder` for resolution
- **Config tokens:** Paths in config files can use `~appFolder` and `~dataFolder` tokens, and a leading `~/` (or `~\` on Windows) expands to the home directory
- **MCP Server:** Use [@devproxy/mcp](https://www.npmjs.com/package/@devproxy/mcp) to programmatically retrieve up-to-date docs and JSON schemas

## Best Practices
Expand Down
41 changes: 41 additions & 0 deletions DevProxy.Abstractions.Tests/Utils/ProxyUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,45 @@ public void NormalizeWebSocketScheme_DoesNotRewriteSchemeInPath() =>
[Fact]
public void NormalizeWebSocketScheme_NullThrows() =>
Assert.Throws<System.ArgumentNullException>(() => ProxyUtils.NormalizeWebSocketScheme(null!));

[Fact]
public void ReplacePathTokens_ExpandsLeadingTildeSlashToHomeDirectory()
{
var home = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
Assert.Equal(System.IO.Path.Join(home, "mocks/mocks.json"), ProxyUtils.ReplacePathTokens("~/mocks/mocks.json"));
}

[Fact]
public void ReplacePathTokens_ExpandsTildeSlashOnlyToHomeDirectory()
{
var home = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
Assert.Equal(home, ProxyUtils.ReplacePathTokens("~/"));
}

[Fact]
public void ReplacePathTokens_ExpandsLeadingTildeBackslashOnWindowsOnly()
{
var home = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
var expected = System.OperatingSystem.IsWindows()
? System.IO.Path.Join(home, @"mocks\mocks.json")
: @"~\mocks\mocks.json";
Assert.Equal(expected, ProxyUtils.ReplacePathTokens(@"~\mocks\mocks.json"));
}

[Theory]
[InlineData("~mocks/mocks.json")]
[InlineData("mocks/~/mocks.json")]
[InlineData("~")]
public void ReplacePathTokens_DoesNotExpandNonLeadingOrBareTilde(string input) =>
Assert.Equal(input, ProxyUtils.ReplacePathTokens(input));

[Fact]
public void GetFullPath_ResolvesTildeSlashToHomeDirectoryInsteadOfConfigFolder()
{
var home = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
var configFile = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "devproxyrc.json");
Assert.Equal(
System.IO.Path.GetFullPath(System.IO.Path.Combine(home, "mocks", "mocks.json")),
ProxyUtils.GetFullPath("~/mocks/mocks.json", configFile));
}
}
18 changes: 18 additions & 0 deletions DevProxy.Abstractions/Utils/ProxyUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,28 @@ public static string ReplacePathTokens(string? path)
return path ?? string.Empty;
}

path = ExpandHomeDirectory(path);
path = path.Replace("~dataFolder", DataFolder, StringComparison.OrdinalIgnoreCase);
return path.Replace("~appFolder", AppFolder, StringComparison.OrdinalIgnoreCase);
}

private static string ExpandHomeDirectory(string path)
{
if (path.Length < 2 || path[0] != '~' ||
(path[1] != '/' && !(OperatingSystem.IsWindows() && path[1] == '\\')))
{
return path;
}

var homeFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (string.IsNullOrEmpty(homeFolder))
{
return path;
}

return Path.Join(homeFolder, path[2..]);
}

public static string GetFullPath(string? path, string? basePath) =>
Path.GetFullPath(ReplacePathTokens(path), Path.GetDirectoryName(basePath) ?? string.Empty);

Expand Down
2 changes: 1 addition & 1 deletion DevProxy/Commands/DevProxyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ sealed class DevProxyCommand : RootCommand
internal static readonly Option<string?> 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 and ~dataFolder tokens."
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, and a leading ~/ for the home directory."
};
internal const string NoFirstRunOptionName = "--no-first-run";
internal const string NoWatchOptionName = "--no-watch";
Expand Down
2 changes: 1 addition & 1 deletion skills/dev-proxy/references/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ A configuration file follows a specific property order: `$schema`, then `plugins
- Include `$schema` in both the root config and each plugin config section for validation.
- The `pluginPath` is always `~appFolder/plugins/DevProxy.Plugins.dll` — `~appFolder` resolves to the Dev Proxy installation directory.
- The `configSection` value can be any string. Use different, descriptive names for multiple instances of the same plugin.
- File paths in Dev Proxy configuration files are always relative to the file where they are defined.
- File paths in Dev Proxy configuration files are relative to the file where they are defined, unless they start with `~/` (or `~\` on Windows), which expands to the user's home directory.
- Configuration hot-reloads on file save (v2.1.0+) — no restart needed. Works for both the main config and plugin-specific files.

## Plugin Architecture
Expand Down
Loading