using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; namespace zero.Backoffice.DevServer; public class ZeroDevService : IHostedService { IWebHostEnvironment env; IOptions options; ProcessProxy viteProcess; ILogger logger; IEnumerable plugins; string workingDirectory; bool isRunning = false; public ZeroDevService(IWebHostEnvironment env, IOptions options, ILogger logger, IEnumerable plugins) { //foreach (IZeroPlugin plugin in plugins) //{ // string location = Assembly.GetAssembly(plugin.GetType()). ; //} this.plugins = plugins; this.env = env; this.options = options; this.workingDirectory = options.Value.WorkingDirectory; this.logger = logger; } public async Task StartAsync(CancellationToken cancellationToken) { // this is a development-time service, // therefore no way to enable it in production if (!env.IsDevelopment() || !options.Value.Enabled) { return; } // locate npm version and throw if it is not installed Version npmVersion = await FindNpmVersion(); if (npmVersion == null) { throw new Exception("Please install node+npm to use the zero dev service (https://www.npmjs.com/)"); } // start vite server viteProcess = await StartDevServer(options.Value.Port); logger.LogInformation("vite listening on: http://localhost:{port}", options.Value.Port); } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } /// /// Get the node version from the system /// async Task FindNpmVersion() { Version version = null; ProcessProxy process = new ProcessProxy(workingDirectory, "npm").Argument("-v").Capture((value, err) => { if (version == null && !value.Contains("not recognized") && Version.TryParse(value, out Version _version)) { version = _version; } }); await process.ExecuteAsync(); return version; } /// /// Starts the vite dev server which also support HMR /// async Task StartDevServer(int port) { // if the port we want to use is occupied, terminate the process utilizing that port. // this occurs when "stop" is used from the debugger and the middleware does not have the opportunity to kill the process PidUtils.KillPort((ushort)port, true); // get all plugins which need to be passed to vite List plugins = new(); foreach (var plugin in this.plugins) { if (!String.IsNullOrEmpty(plugin.Options.PluginPath)) { plugins.Add(plugin.Options.PluginPath); } } // create and run the vite script ProcessProxy process = new ProcessProxy(workingDirectory, "npm", options.Value.ForwardLog) .Argument("run dev") .EnvVar("PORT", port.ToString()) .EnvVar("ZERO_PLUGINS", JsonConvert.SerializeObject(plugins)) .Capture(CaptureLog); await process.RunAsync("localhost:", TimeSpan.FromMinutes(1)); isRunning = true; return process; } void CaptureLog(string line, bool isError) { if (!isRunning) { return; } if (isError) { logger.LogWarning(line); } else { logger.LogInformation(line); } } }