6 Commits

Author SHA1 Message Date
swcs 2b16e2067c 1.3.0 2026-05-11 15:08:40 +02:00
swcs 4ece202cbb remove unnecessary dep 2026-04-30 14:55:29 +02:00
swcs 6bcabcdea8 make config path customizable 2026-03-25 15:42:31 +01:00
swcs 214938b966 do not run proxy service when not in dev env 2026-03-25 12:58:20 +01:00
swcs ae1efdc527 correct prop naming 2026-03-25 12:54:53 +01:00
swcs 7c581583d2 Update ViteProxy.csproj 2024-08-20 14:29:11 +02:00
6 changed files with 91 additions and 80 deletions
+48 -48
View File
@@ -8,27 +8,27 @@ namespace ViteProxy;
public class ProcessProxy
{
string workingDirectory;
string script;
Action<ProcessStartInfo> onProcessConfigure = null;
Action<string, bool> captureLog = null;
bool isWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
Dictionary<string, string> envVars = new();
HashSet<string> arguments = new();
bool forwardLog = false;
readonly string _workingDirectory;
readonly string _script;
Action<ProcessStartInfo> _onProcessConfigure = null;
Action<string, bool> _captureLog = null;
bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
readonly Dictionary<string, string> _envVars = new();
readonly HashSet<string> _arguments = new();
readonly bool _forwardLog = false;
Process process = null;
EventedStreamReader stdOut;
EventedStreamReader stdErr;
Process _process = null;
EventedStreamReader _stdOut;
EventedStreamReader _stdErr;
public ProcessProxy(string workingDirectory, string script, bool forwardLog = false)
{
this.workingDirectory = workingDirectory;
this.script = script;
this.forwardLog = forwardLog;
this._workingDirectory = workingDirectory;
this._script = script;
this._forwardLog = forwardLog;
}
@@ -37,7 +37,7 @@ public class ProcessProxy
/// </summary>
public ProcessProxy EnvVar(string key, string value)
{
envVars.Add(key, value);
_envVars.Add(key, value);
return this;
}
@@ -47,7 +47,7 @@ public class ProcessProxy
/// </summary>
public ProcessProxy Argument(string argument)
{
arguments.Add(argument);
_arguments.Add(argument);
return this;
}
@@ -57,7 +57,7 @@ public class ProcessProxy
/// </summary>
public ProcessProxy Configure(Action<ProcessStartInfo> onProcessConfigure)
{
this.onProcessConfigure = onProcessConfigure;
this._onProcessConfigure = onProcessConfigure;
return this;
}
@@ -67,7 +67,7 @@ public class ProcessProxy
/// </summary>
public ProcessProxy Capture(Action<string, bool> action)
{
this.captureLog = action;
this._captureLog = action;
return this;
}
@@ -80,16 +80,16 @@ public class ProcessProxy
{
StartProcess();
using var stdErrReader = new EventedStreamStringReader(stdErr);
using var stdErrReader = new EventedStreamStringReader(_stdErr);
try
{
// TODO implement timeout
// https://stackoverflow.com/questions/18760252/timeout-an-async-method-implemented-with-taskcompletionsource
await stdOut.WaitForFinish();
await _stdOut.WaitForFinish();
}
catch (EndOfStreamException ex)
{
throw new InvalidOperationException($"The script '{script}' exited without indicating that the server was listening for requests.\nThe error output was: " + $"{stdErrReader.ReadAsString()}", ex);
throw new InvalidOperationException($"The script '{_script}' exited without indicating that the server was listening for requests.\nThe error output was: " + $"{stdErrReader.ReadAsString()}", ex);
}
}
@@ -102,22 +102,22 @@ public class ProcessProxy
{
StartProcess();
using var stdErrReader = new EventedStreamStringReader(stdErr);
using var stdErrReader = new EventedStreamStringReader(_stdErr);
try
{
await stdOut.WaitForMatch(new Regex(startupCondition, RegexOptions.IgnoreCase, startupTimeout == default ? TimeSpan.FromMinutes(5) : startupTimeout));
await _stdOut.WaitForMatch(new Regex(startupCondition, RegexOptions.IgnoreCase, startupTimeout == default ? TimeSpan.FromMinutes(5) : startupTimeout));
}
catch (EndOfStreamException ex)
{
throw new InvalidOperationException($"The script '{script}' exited without indicating that the server was listening for requests.\nThe error output was: " + $"{stdErrReader.ReadAsString()}", ex);
throw new InvalidOperationException($"The script '{_script}' exited without indicating that the server was listening for requests.\nThe error output was: " + $"{stdErrReader.ReadAsString()}", ex);
}
}
public void Exit()
{
try { process?.Kill(); } catch { }
try { process?.WaitForExit(); } catch { }
try { _process?.Kill(); } catch { }
try { _process?.WaitForExit(); } catch { }
AppDomain.CurrentDomain.DomainUnload -= UnloadHandler;
AppDomain.CurrentDomain.ProcessExit -= UnloadHandler;
@@ -130,18 +130,18 @@ public class ProcessProxy
/// </summary>
Process StartProcess()
{
string executable = script;
string executable = _script;
StringBuilder command = new();
command.Append(script);
command.Append(_script);
command.Append(' ');
foreach (string arg in arguments)
foreach (string arg in _arguments)
{
command.Append(arg);
}
string argumentList = command.ToString();
if (isWindows)
if (IsWindows)
{
argumentList = $"/c {argumentList}";
executable = "cmd";
@@ -154,20 +154,20 @@ public class ProcessProxy
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = workingDirectory
WorkingDirectory = _workingDirectory
};
foreach (var envVar in envVars)
foreach (var envVar in _envVars)
{
startInfo.Environment[envVar.Key] = envVar.Value;
}
onProcessConfigure?.Invoke(startInfo);
_onProcessConfigure?.Invoke(startInfo);
try
{
process = Process.Start(startInfo);
process.EnableRaisingEvents = true;
_process = Process.Start(startInfo);
_process.EnableRaisingEvents = true;
}
catch (Exception ex)
{
@@ -185,7 +185,7 @@ public class ProcessProxy
AppDomain.CurrentDomain.ProcessExit += UnloadHandler;
AppDomain.CurrentDomain.UnhandledException += UnloadHandler;
return process;
return _process;
}
@@ -197,13 +197,13 @@ public class ProcessProxy
void AttachLogger(bool isStream = false)
{
stdOut = new EventedStreamReader(process.StandardOutput);
stdErr = new EventedStreamReader(process.StandardError);
_stdOut = new EventedStreamReader(_process.StandardOutput);
_stdErr = new EventedStreamReader(_process.StandardError);
stdOut.OnReceivedLine += line => WriteToLog(line);
stdOut.OnReceivedChunk += chunk => WriteToLog(chunk);
stdErr.OnReceivedLine += line => WriteToLog(line, true);
stdErr.OnReceivedChunk += chunk => WriteToLog(chunk, true);
_stdOut.OnReceivedLine += line => WriteToLog(line);
_stdOut.OnReceivedChunk += chunk => WriteToLog(chunk);
_stdErr.OnReceivedLine += line => WriteToLog(line, true);
_stdErr.OnReceivedChunk += chunk => WriteToLog(chunk, true);
}
@@ -216,11 +216,11 @@ public class ProcessProxy
line = line.StartsWith("<s>") ? line.Substring(3) : line;
if (captureLog != null)
if (_captureLog != null)
{
captureLog(line, isError);
_captureLog(line, isError);
}
if (forwardLog)
if (_forwardLog)
{
(isError ? Console.Error : Console.Out).WriteLine(line);
}
@@ -236,11 +236,11 @@ public class ProcessProxy
return;
}
if (captureLog != null)
if (_captureLog != null)
{
captureLog(new String(chunk.Array, chunk.Offset, chunk.Count), isError);
_captureLog(new String(chunk.Array, chunk.Offset, chunk.Count), isError);
}
if (forwardLog)
if (_forwardLog)
{
(isError ? Console.Error : Console.Out).Write(chunk.Array, chunk.Offset, chunk.Count);
}
+10 -9
View File
@@ -1,18 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>ViteProxy</RootNamespace>
<AssemblyName>ViteProxy</AssemblyName>
<Version>1.2.0</Version>
<DebugType>embedded</DebugType>
<Version>1.3.0</Version>
</PropertyGroup>
<ItemGroup>
<None Include="../viteproxy-logo.png" Pack="true" PackagePath="\" />
<None Include="../README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup>
<Product>ViteProxy</Product>
<Title>ViteProxy</Title>
<Copyright>Copyright © Tobias Klika 2024</Copyright>
<Copyright>Copyright © Tobias Klika 2026</Copyright>
<Tags>Vite, npm, middleware, server, development</Tags>
<Authors>Tobias Klika</Authors>
<Description>ViteProxy is a proxy for vite projects within ASP.NET Core.</Description>
@@ -21,7 +25,8 @@
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageId>ViteProxy</PackageId>
<PackageProjectUrl>https://github.com/ceee/ViteProxy</PackageProjectUrl>
<PackageIconUrl>https://raw.githubusercontent.com/ceee/ViteProxy/main/viteproxy-logo.png</PackageIconUrl>
<PackageIcon>viteproxy-logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
@@ -32,9 +37,5 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
</Project>
@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace ViteProxy;
@@ -19,6 +20,12 @@ public static class ViteProxyApplicationBuilderExtensions
{
IWebHostEnvironment env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
// do not provide static files when not in development
if (!env.IsDevelopment())
{
return app;
}
if (path == null && configName == null)
{
IOptions<ViteProxyOptions> options = app.ApplicationServices.GetRequiredService<IOptions<ViteProxyOptions>>();
+1 -1
View File
@@ -15,7 +15,7 @@ public class ViteProxyOptions
/// <summary>
/// Directory where the frontend files are located
/// </summary>
public string WorkingDirectory { get; set; }
public string WorkingDirectory { get; set; } = "App";
/// <summary>
/// Whether to enable the proxy or not
+21 -18
View File
@@ -7,26 +7,28 @@ namespace ViteProxy;
public class ViteProxyService : IHostedService
{
ViteProxyOptions options;
ViteWorkingDirectory workingDirectory;
ILogger<ViteProxyService> logger;
bool isRunning = false;
readonly ViteProxyOptions _options;
readonly ViteWorkingDirectory _workingDirectory;
readonly ILogger<ViteProxyService> _logger;
readonly IWebHostEnvironment _env;
bool _isRunning = false;
public ViteProxyService(IWebHostEnvironment env, ILogger<ViteProxyService> logger, IOptions<ViteProxyOptions> options) : this(env, logger, options.Value ?? new()) { }
public ViteProxyService(IWebHostEnvironment env, ILogger<ViteProxyService> logger, ViteProxyOptions options)
{
this.options = options ?? new();
this.workingDirectory = new ViteWorkingDirectory(env, options.WorkingDirectory);
this.logger = logger;
_options = options ?? new();
_workingDirectory = new ViteWorkingDirectory(env, _options.WorkingDirectory);
_env = env;
_logger = logger;
}
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!this.options.Enabled)
if (!_options.Enabled || !_env.IsDevelopment())
{
return;
}
@@ -35,8 +37,8 @@ public class ViteProxyService : IHostedService
Version npmVersion = await FindNpmVersion() ?? throw new Exception("Please install node+npm to use the vite dev service (https://www.npmjs.com/)");
// start vite server
ProcessProxy viteProcess = await StartDevServer(options.Port);
logger.LogInformation("vite listening on: http://localhost:{port} (path: {workingDirectory})", options.Port, this.workingDirectory.Path);
ProcessProxy viteProcess = await StartDevServer(_options.Port);
_logger.LogInformation("vite listening on: http://localhost:{port} (path: {workingDirectory})", _options.Port, _workingDirectory.Path);
}
public Task StopAsync(CancellationToken cancellationToken)
@@ -52,7 +54,7 @@ public class ViteProxyService : IHostedService
{
Version version = null;
ProcessProxy process = new ProcessProxy(workingDirectory.Path, "npm").Argument("-v").Capture((value, err) =>
ProcessProxy process = new ProcessProxy(_workingDirectory.Path, "npm").Argument("-v").Capture((value, err) =>
{
if (version == null && !value.Contains("not recognized") && Version.TryParse(value, out Version _version))
{
@@ -76,14 +78,15 @@ public class ViteProxyService : IHostedService
PidUtils.KillPort((ushort)port, true);
// create and run the vite script
ProcessProxy process = new ProcessProxy(workingDirectory.Path, "npm", options.ForwardLog)
.Argument("run " + options.ScriptName)
ProcessProxy process = new ProcessProxy(_workingDirectory.Path, "npm", _options.ForwardLog)
.Argument("run " + _options.ScriptName)
.EnvVar("PORT", port.ToString())
//.EnvVar("URL_PREFIX", "/@viteproxy/")
.Capture(CaptureLog);
await process.RunAsync(options.StartupCondition, TimeSpan.FromSeconds(options.TimeoutInSeconds));
await process.RunAsync(_options.StartupCondition, TimeSpan.FromSeconds(_options.TimeoutInSeconds));
isRunning = true;
_isRunning = true;
return process;
}
@@ -91,18 +94,18 @@ public class ViteProxyService : IHostedService
void CaptureLog(string line, bool isError)
{
if (!isRunning)
if (!_isRunning)
{
return;
}
if (isError)
{
logger.LogWarning(line);
_logger.LogWarning(line);
}
else
{
logger.LogInformation(line);
_logger.LogInformation(line);
}
}
}
+4 -4
View File
@@ -8,23 +8,23 @@ namespace ViteProxy;
public static class ViteProxyServiceCollectionExtensions
{
public static IServiceCollection AddViteProxy(this IServiceCollection services)
public static IServiceCollection AddViteProxy(this IServiceCollection services, string configSectionPath = "Vite")
{
services.AddLogging();
services.AddOptions();
services.AddHostedService<ViteProxyService>();
services.AddOptions<ViteProxyOptions>().BindConfiguration("Vite");
services.AddOptions<ViteProxyOptions>().BindConfiguration(configSectionPath);
return services;
}
public static IServiceCollection AddViteProxy(this IServiceCollection services, Action<ViteProxyOptions> configure)
public static IServiceCollection AddViteProxy(this IServiceCollection services, Action<ViteProxyOptions> configure, string configSectionPath = "Vite")
{
services.AddLogging();
services.AddOptions();
services.AddHostedService<ViteProxyService>();
services.AddOptions<ViteProxyOptions>().BindConfiguration("Vite");
services.AddOptions<ViteProxyOptions>().BindConfiguration(configSectionPath);
services.PostConfigure(configure);
return services;