2020-11-01 23:23:36 +01:00
|
|
|
using Microsoft.AspNetCore.Hosting;
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
2020-11-03 00:49:41 +01:00
|
|
|
using System.Reflection;
|
2020-11-01 23:23:36 +01:00
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2020-11-03 00:49:41 +01:00
|
|
|
using zero.Core.Plugins;
|
2020-11-01 23:23:36 +01:00
|
|
|
|
|
|
|
|
namespace Zero.Web.DevServer
|
|
|
|
|
{
|
|
|
|
|
public class ZeroDevService : IHostedService
|
|
|
|
|
{
|
|
|
|
|
IWebHostEnvironment env;
|
|
|
|
|
IOptions<ZeroDevOptions> options;
|
2020-11-03 00:49:41 +01:00
|
|
|
ProcessProxy viteProcess;
|
2020-11-01 23:23:36 +01:00
|
|
|
ILogger<ZeroDevService> logger;
|
2020-11-03 00:49:41 +01:00
|
|
|
PluginResolver pluginResolver;
|
2020-11-01 23:23:36 +01:00
|
|
|
string workingDirectory;
|
2020-11-03 00:49:41 +01:00
|
|
|
bool isRunning = false;
|
2020-11-01 23:23:36 +01:00
|
|
|
|
|
|
|
|
|
2020-11-03 00:49:41 +01:00
|
|
|
public ZeroDevService(IWebHostEnvironment env, IOptions<ZeroDevOptions> options, ILogger<ZeroDevService> logger, IEnumerable<IZeroPlugin> plugins)
|
2020-11-01 23:23:36 +01:00
|
|
|
{
|
2020-11-03 00:49:41 +01:00
|
|
|
//foreach (IZeroPlugin plugin in plugins)
|
|
|
|
|
//{
|
|
|
|
|
// string location = Assembly.GetAssembly(plugin.GetType()). ;
|
|
|
|
|
//}
|
|
|
|
|
this.pluginResolver = new PluginResolver(plugins);
|
2020-11-01 23:23:36 +01:00
|
|
|
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
|
2021-11-12 15:16:48 +01:00
|
|
|
if (!env.IsDevelopment() || !options.Value.Enabled)
|
2020-11-01 23:23:36 +01:00
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-03 00:49:41 +01:00
|
|
|
// locate npm version and throw if it is not installed
|
2020-11-01 23:23:36 +01:00
|
|
|
Version npmVersion = await FindNpmVersion();
|
|
|
|
|
|
|
|
|
|
if (npmVersion == null)
|
|
|
|
|
{
|
2020-11-03 00:49:41 +01:00
|
|
|
throw new Exception("Please install node+npm to use the zero dev service (https://www.npmjs.com/)");
|
2020-11-01 23:23:36 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-03 00:49:41 +01:00
|
|
|
// start vite server
|
2020-11-01 23:23:36 +01:00
|
|
|
viteProcess = await StartDevServer(options.Value.Port);
|
2020-11-03 00:49:41 +01:00
|
|
|
logger.LogInformation("vite listening on: http://localhost:{port}", options.Value.Port);
|
2020-11-01 23:23:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get the node version from the system
|
|
|
|
|
/// </summary>
|
|
|
|
|
async Task<Version> FindNpmVersion()
|
|
|
|
|
{
|
|
|
|
|
Version version = null;
|
|
|
|
|
|
2020-11-03 00:49:41 +01:00
|
|
|
ProcessProxy process = new ProcessProxy(workingDirectory, "npm").Argument("-v").Capture((value, err) =>
|
2020-11-01 23:23:36 +01:00
|
|
|
{
|
|
|
|
|
if (version == null && !value.Contains("not recognized") && Version.TryParse(value, out Version _version))
|
|
|
|
|
{
|
|
|
|
|
version = _version;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await process.ExecuteAsync();
|
|
|
|
|
|
|
|
|
|
return version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Starts the vite dev server which also support HMR
|
|
|
|
|
/// </summary>
|
2020-11-03 00:49:41 +01:00
|
|
|
async Task<ProcessProxy> StartDevServer(int port)
|
2020-11-01 23:23:36 +01:00
|
|
|
{
|
|
|
|
|
// 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
|
2020-12-02 22:25:20 +01:00
|
|
|
List<string> plugins = new();
|
|
|
|
|
|
|
|
|
|
foreach (var plugin in pluginResolver.Plugins)
|
2020-11-01 23:23:36 +01:00
|
|
|
{
|
2020-12-02 22:25:20 +01:00
|
|
|
if (!String.IsNullOrEmpty(plugin.Options.PluginPath))
|
2020-11-01 23:23:36 +01:00
|
|
|
{
|
2020-12-02 22:25:20 +01:00
|
|
|
plugins.Add(plugin.Options.PluginPath);
|
2020-11-01 23:23:36 +01:00
|
|
|
}
|
2020-12-02 22:25:20 +01:00
|
|
|
}
|
2020-11-01 23:23:36 +01:00
|
|
|
|
|
|
|
|
// create and run the vite script
|
2020-11-03 00:49:41 +01:00
|
|
|
ProcessProxy process = new ProcessProxy(workingDirectory, "npm", options.Value.ForwardLog)
|
2020-11-01 23:23:36 +01:00
|
|
|
.Argument("run dev")
|
2020-11-03 00:49:41 +01:00
|
|
|
.EnvVar("PORT", port.ToString())
|
|
|
|
|
.EnvVar("ZERO_PLUGINS", JsonConvert.SerializeObject(plugins))
|
|
|
|
|
.Capture(CaptureLog);
|
2020-11-01 23:23:36 +01:00
|
|
|
|
2020-11-03 00:49:41 +01:00
|
|
|
await process.RunAsync("localhost:", TimeSpan.FromMinutes(1));
|
|
|
|
|
|
|
|
|
|
isRunning = true;
|
2020-11-01 23:23:36 +01:00
|
|
|
|
|
|
|
|
return process;
|
|
|
|
|
}
|
2020-11-03 00:49:41 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
void CaptureLog(string line, bool isError)
|
|
|
|
|
{
|
|
|
|
|
if (!isRunning)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isError)
|
|
|
|
|
{
|
|
|
|
|
logger.LogWarning(line);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
logger.LogInformation(line);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-01 23:23:36 +01:00
|
|
|
}
|
|
|
|
|
}
|