From 96bb3a5c0f2474ae513cec381657d06850fb43dd Mon Sep 17 00:00:00 2001 From: Masu Baumgartner <68913099+Masu-Baumgartner@users.noreply.github.com> Date: Sun, 10 Nov 2024 20:36:02 +0100 Subject: [PATCH] Preparations for plugin/module development --- Moonlight.ApiServer/DevServer.cs | 6 +- .../Moonlight.ApiServer.csproj | 10 + Moonlight.ApiServer/Program.cs | 198 --------------- Moonlight.ApiServer/Startup.cs | 226 +++++++++++++++++- Moonlight.Client/DevClient.cs | 12 + Moonlight.Client/Moonlight.Client.csproj | 10 + Moonlight.Client/Program.cs | 127 +--------- Moonlight.Client/Startup.cs | 131 ++++++++++ Moonlight.Shared/Moonlight.Shared.csproj | 11 + 9 files changed, 393 insertions(+), 338 deletions(-) delete mode 100644 Moonlight.ApiServer/Program.cs create mode 100644 Moonlight.Client/DevClient.cs create mode 100644 Moonlight.Client/Startup.cs diff --git a/Moonlight.ApiServer/DevServer.cs b/Moonlight.ApiServer/DevServer.cs index 2e7002bb..40858309 100644 --- a/Moonlight.ApiServer/DevServer.cs +++ b/Moonlight.ApiServer/DevServer.cs @@ -1,10 +1,12 @@ +using System.Reflection; + namespace Moonlight.ApiServer; public static class DevServer { - public async static Task Run(string[] args) + public async static Task Run(string[] args, Assembly[] pluginAssemblies) { Console.WriteLine("Preparing development server"); - await Program.Main(args); + await Startup.Run(args, pluginAssemblies); } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index be019081..3c5b42a4 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -5,6 +5,16 @@ enable enable Linux + true + 2.1.0 + Moonlight.ApiServer + Moonlight Panel + A build of moonlight's api server as a nuget package to develop moonlight plugins/modules + Moonlight Panel + https://github.com/Moonlight-Panel/Moonlight + https://github.com/Moonlight-Panel/Moonlight + git + moonlight diff --git a/Moonlight.ApiServer/Program.cs b/Moonlight.ApiServer/Program.cs deleted file mode 100644 index 0e27d33a..00000000 --- a/Moonlight.ApiServer/Program.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System.Reflection; -using MoonCore.Extended.Extensions; -using MoonCore.Extensions; -using MoonCore.Helpers; -using MoonCore.PluginFramework.Extensions; -using Moonlight.ApiServer; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Helpers; -using Moonlight.ApiServer.Http.Middleware; -using Moonlight.ApiServer.Interfaces.Auth; -using Moonlight.ApiServer.Interfaces.OAuth2; -using Moonlight.ApiServer.Interfaces.Startup; - -public class Program -{ - public static async Task Main(string[] args) - { - // Cry about it - #pragma warning disable ASP0000 - - // Fancy start console output... yes very fancy :> - var rainbow = new Crayon.Rainbow(0.5); - foreach (var c in "Moonlight") - { - Console.Write( - rainbow - .Next() - .Bold() - .Text(c.ToString()) - ); - } - - Console.WriteLine(); - -// Storage i guess - Directory.CreateDirectory(PathBuilder.Dir("storage")); - -// TODO: Load plugin/module assemblies - -// Configure startup logger - var startupLoggerFactory = new LoggerFactory(); - -// TODO: Add direct extension method - var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => - { - configuration.Console.Enable = true; - configuration.Console.EnableAnsiMode = true; - configuration.FileLogging.Enable = false; - }); - - startupLoggerFactory.AddProviders(providers); - - var startupLogger = startupLoggerFactory.CreateLogger("Startup"); - -// Configure startup interfaces - var startupServiceCollection = new ServiceCollection(); - - startupServiceCollection.AddConfiguration(options => - { - options.UsePath(PathBuilder.Dir("storage")); - options.UseEnvironmentPrefix("MOONLIGHT"); - - options.AddConfiguration("app"); - }); - - startupServiceCollection.AddLogging(loggingBuilder => { loggingBuilder.AddProviders(providers); }); - - startupServiceCollection.AddPlugins(configuration => - { - // Configure startup interfaces - configuration.AddInterface(); - configuration.AddInterface(); - configuration.AddInterface(); - - // Configure assemblies to scan - configuration.AddAssembly(typeof(Program).Assembly); - }); - - - var startupServiceProvider = startupServiceCollection.BuildServiceProvider(); - var appStartupInterfaces = startupServiceProvider.GetRequiredService(); - - var config = startupServiceProvider.GetRequiredService(); - ApplicationStateHelper.SetConfiguration(config); - -// Start the actual app - var builder = WebApplication.CreateBuilder(args); - - await Startup.ConfigureLogging(builder); - - await Startup.ConfigureDatabase( - builder, - startupLoggerFactory, - startupServiceProvider.GetRequiredService() - ); - -// Call interfaces - foreach (var startupInterface in appStartupInterfaces) - { - try - { - await startupInterface.BuildApp(builder); - } - catch (Exception e) - { - startupLogger.LogCritical( - "An unhandled error occured while processing BuildApp call for interface '{interfaceName}': {e}", - startupInterface.GetType().FullName, - e - ); - } - } - - builder.Services.AddControllers(); - builder.Services.AddSingleton(config); - builder.Services.AutoAddServices(); - builder.Services.AddHttpClient(); - - await Startup.ConfigureTokenAuthentication(builder, config); - await Startup.ConfigureOAuth2(builder, startupLogger, config); - -// Implementation service - builder.Services.AddPlugins(configuration => - { - configuration.AddInterface(); - configuration.AddInterface(); - - configuration.AddAssembly(typeof(Program).Assembly); - }); - - var app = builder.Build(); - - await Startup.PrepareDatabase(app); - - if (config.Client.Enable) - { - if (app.Environment.IsDevelopment()) - app.UseWebAssemblyDebugging(); - - app.UseBlazorFrameworkFiles(); - app.UseStaticFiles(); - } - - app.UseRouting(); - - app.UseApiErrorHandling(); - - await Startup.UseTokenAuthentication(app); - await Startup.UseOAuth2(app); - -// Call interfaces - foreach (var startupInterface in appStartupInterfaces) - { - try - { - await startupInterface.ConfigureApp(app); - } - catch (Exception e) - { - startupLogger.LogCritical( - "An unhandled error occured while processing ConfigureApp call for interface '{interfaceName}': {e}", - startupInterface.GetType().FullName, - e - ); - } - } - - app.UseMiddleware(); - - app.UseMiddleware(); - -// Call interfaces - var endpointStartupInterfaces = startupServiceProvider.GetRequiredService(); - - foreach (var endpointStartup in endpointStartupInterfaces) - { - try - { - await endpointStartup.ConfigureEndpoints(app); - } - catch (Exception e) - { - startupLogger.LogCritical( - "An unhandled error occured while processing ConfigureEndpoints call for interface '{interfaceName}': {e}", - endpointStartup.GetType().FullName, - e - ); - } - } - - app.MapControllers(); - - if (config.Client.Enable) - app.MapFallbackToFile("index.html"); - - await app.RunAsync(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup.cs b/Moonlight.ApiServer/Startup.cs index b07862f6..f9188934 100644 --- a/Moonlight.ApiServer/Startup.cs +++ b/Moonlight.ApiServer/Startup.cs @@ -1,3 +1,4 @@ +using System.Reflection; using System.Text.Json; using MoonCore.Authentication; using MoonCore.Exceptions; @@ -7,9 +8,12 @@ using MoonCore.Extended.Helpers; using MoonCore.Extended.OAuth2.Consumer; using MoonCore.Extensions; using MoonCore.Helpers; +using MoonCore.PluginFramework.Extensions; using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Helpers; +using Moonlight.ApiServer.Http.Middleware; +using Moonlight.ApiServer.Interfaces.Auth; using Moonlight.ApiServer.Interfaces.OAuth2; using Moonlight.ApiServer.Interfaces.Startup; using Moonlight.Shared.Http.Responses.OAuth2; @@ -18,6 +22,202 @@ namespace Moonlight.ApiServer; public static class Startup { + public static async Task Main(string[] args) + => await Run(args, []); + + public static async Task Run(string[] args, Assembly[]? pluginAssemblies = null) + { + // Cry about it +#pragma warning disable ASP0000 + + // Fancy start console output... yes very fancy :> + var rainbow = new Crayon.Rainbow(0.5); + foreach (var c in "Moonlight") + { + Console.Write( + rainbow + .Next() + .Bold() + .Text(c.ToString()) + ); + } + + Console.WriteLine(); + +// Storage i guess + Directory.CreateDirectory(PathBuilder.Dir("storage")); + +// TODO: Load plugin/module assemblies + +// Configure startup logger + var startupLoggerFactory = new LoggerFactory(); + +// TODO: Add direct extension method + var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => + { + configuration.Console.Enable = true; + configuration.Console.EnableAnsiMode = true; + configuration.FileLogging.Enable = false; + }); + + startupLoggerFactory.AddProviders(providers); + + var startupLogger = startupLoggerFactory.CreateLogger("Startup"); + +// Configure startup interfaces + var startupServiceCollection = new ServiceCollection(); + + startupServiceCollection.AddConfiguration(options => + { + options.UsePath(PathBuilder.Dir("storage")); + options.UseEnvironmentPrefix("MOONLIGHT"); + + options.AddConfiguration("app"); + }); + + startupServiceCollection.AddLogging(loggingBuilder => { loggingBuilder.AddProviders(providers); }); + + startupServiceCollection.AddPlugins(configuration => + { + // Configure startup interfaces + configuration.AddInterface(); + configuration.AddInterface(); + configuration.AddInterface(); + + // Configure assemblies to scan + configuration.AddAssembly(typeof(Startup).Assembly); + + if(pluginAssemblies != null) + configuration.AddAssemblies(pluginAssemblies); + + //TODO: Load plugins from file + }); + + + var startupServiceProvider = startupServiceCollection.BuildServiceProvider(); + var appStartupInterfaces = startupServiceProvider.GetRequiredService(); + + var config = startupServiceProvider.GetRequiredService(); + ApplicationStateHelper.SetConfiguration(config); + +// Start the actual app + var builder = WebApplication.CreateBuilder(args); + + await ConfigureLogging(builder); + + await ConfigureDatabase( + builder, + startupLoggerFactory, + startupServiceProvider.GetRequiredService() + ); + +// Call interfaces + foreach (var startupInterface in appStartupInterfaces) + { + try + { + await startupInterface.BuildApp(builder); + } + catch (Exception e) + { + startupLogger.LogCritical( + "An unhandled error occured while processing BuildApp call for interface '{interfaceName}': {e}", + startupInterface.GetType().FullName, + e + ); + } + } + + builder.Services.AddControllers(); + builder.Services.AddSingleton(config); + builder.Services.AutoAddServices(typeof(Startup).Assembly); + builder.Services.AddHttpClient(); + + await ConfigureTokenAuthentication(builder, config); + await ConfigureOAuth2(builder, startupLogger, config); + + // Implementation service + builder.Services.AddPlugins(configuration => + { + configuration.AddInterface(); + configuration.AddInterface(); + + configuration.AddAssembly(typeof(Startup).Assembly); + + if(pluginAssemblies != null) + configuration.AddAssemblies(pluginAssemblies); + + //TODO: Load plugins from file + }); + + var app = builder.Build(); + + await PrepareDatabase(app); + + if (config.Client.Enable) + { + if (app.Environment.IsDevelopment()) + app.UseWebAssemblyDebugging(); + + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(); + } + + app.UseRouting(); + + app.UseApiErrorHandling(); + + await UseTokenAuthentication(app); + await UseOAuth2(app); + +// Call interfaces + foreach (var startupInterface in appStartupInterfaces) + { + try + { + await startupInterface.ConfigureApp(app); + } + catch (Exception e) + { + startupLogger.LogCritical( + "An unhandled error occured while processing ConfigureApp call for interface '{interfaceName}': {e}", + startupInterface.GetType().FullName, + e + ); + } + } + + app.UseMiddleware(); + + app.UseMiddleware(); + +// Call interfaces + var endpointStartupInterfaces = startupServiceProvider.GetRequiredService(); + + foreach (var endpointStartup in endpointStartupInterfaces) + { + try + { + await endpointStartup.ConfigureEndpoints(app); + } + catch (Exception e) + { + startupLogger.LogCritical( + "An unhandled error occured while processing ConfigureEndpoints call for interface '{interfaceName}': {e}", + endpointStartup.GetType().FullName, + e + ); + } + } + + app.MapControllers(); + + if (config.Client.Enable) + app.MapFallbackToFile("index.html"); + + await app.RunAsync(); + } + #region Logging public static async Task ConfigureLogging(IHostApplicationBuilder builder) @@ -132,7 +332,7 @@ public static class Startup authenticationConfig.ProcessRefresh = async (oldData, newData, serviceProvider) => { var oauth2Providers = serviceProvider.GetRequiredService(); - + // Find oauth2 provider var provider = oauth2Providers.FirstOrDefault(); @@ -145,10 +345,10 @@ public static class Startup { return false; } - + // Load user from database if existent var userRepo = serviceProvider.GetRequiredService>(); - + var user = userRepo .Get() .FirstOrDefault(x => x.Id == userId); @@ -159,12 +359,12 @@ public static class Startup // Allow plugins to intercept the refresh call //if (AuthInterceptors.Any(interceptor => !interceptor.AllowRefresh(user, serviceProvider))) // return false; - + // Check if it's time to resync with the oauth2 provider if (DateTime.UtcNow >= user.RefreshTimestamp) { var oAuth2Service = serviceProvider.GetRequiredService(); - + try { // It's time to refresh the access to the external oauth2 provider @@ -194,7 +394,7 @@ public static class Startup { var loggerFactory = serviceProvider.GetRequiredService(); var logger = loggerFactory.CreateLogger("OAuth2 Refresh"); - + // We are handling this error more softly, because it will occur when a user hasn't logged in a long period of time logger.LogTrace("An error occured while refreshing external oauth2 access: {e}", e); return false; @@ -213,7 +413,7 @@ public static class Startup public static Task UseTokenAuthentication(WebApplication builder) { builder.UseTokenAuthentication(); - + return Task.CompletedTask; } @@ -289,13 +489,13 @@ public static class Startup configuration.ProcessComplete = async (serviceProvider, accessData) => { var oauth2Providers = serviceProvider.GetRequiredService(); - + // Find oauth2 provider var provider = oauth2Providers.FirstOrDefault(); if (provider == null) throw new HttpApiException("No oauth2 provider has been registered", 500); - + try { var user = await provider.Sync(serviceProvider, accessData.AccessToken); @@ -313,14 +513,14 @@ public static class Startup return new Dictionary() { - {"userId", user.Id} + { "userId", user.Id } }; } catch (Exception e) { var loggerFactory = serviceProvider.GetRequiredService(); var logger = loggerFactory.CreateLogger(provider.GetType()); - + logger.LogTrace("An error occured while syncing user with oauth2 provider: {e}", e); throw new HttpApiException("Unable to synchronize with oauth2 provider", 400); } @@ -351,9 +551,9 @@ public static class Startup public static Task UseOAuth2(WebApplication application) { application.UseOAuth2Consumer(); - + return Task.CompletedTask; } - + #endregion } \ No newline at end of file diff --git a/Moonlight.Client/DevClient.cs b/Moonlight.Client/DevClient.cs new file mode 100644 index 00000000..5212ddc7 --- /dev/null +++ b/Moonlight.Client/DevClient.cs @@ -0,0 +1,12 @@ +using System.Reflection; + +namespace Moonlight.Client; + +public class DevClient +{ + public async static Task Run(string[] args, Assembly[] assemblies) + { + Console.WriteLine("Preparing development client"); + await Startup.Run(args, assemblies); + } +} \ No newline at end of file diff --git a/Moonlight.Client/Moonlight.Client.csproj b/Moonlight.Client/Moonlight.Client.csproj index 10fffcf2..b49e662e 100644 --- a/Moonlight.Client/Moonlight.Client.csproj +++ b/Moonlight.Client/Moonlight.Client.csproj @@ -6,6 +6,16 @@ enable service-worker-assets.js Linux + true + 2.1.0 + Moonlight.Client + Moonlight Panel + A build of moonlight's client as a nuget package to develop moonlight plugins/modules + Moonlight Panel + https://github.com/Moonlight-Panel/Moonlight + https://github.com/Moonlight-Panel/Moonlight + git + moonlight diff --git a/Moonlight.Client/Program.cs b/Moonlight.Client/Program.cs index 33a3718f..c30ad99e 100644 --- a/Moonlight.Client/Program.cs +++ b/Moonlight.Client/Program.cs @@ -1,126 +1,3 @@ -using System.Reflection; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using MoonCore.Blazor.Extensions; -using MoonCore.Blazor.Services; -using MoonCore.Blazor.Tailwind.Extensions; -using MoonCore.Blazor.Tailwind.Forms; -using MoonCore.Blazor.Tailwind.Forms.Components; -using MoonCore.Exceptions; -using MoonCore.Extensions; -using MoonCore.Helpers; -using MoonCore.Models; -using MoonCore.PluginFramework.Extensions; -using Moonlight.Client.Interfaces; -using Moonlight.Client.Services; -using Moonlight.Client.UI; -using Moonlight.Client.UI.Forms; -using Moonlight.Shared.Http.Requests.Auth; -using Moonlight.Shared.Http.Responses.Auth; +using Moonlight.Client; -// Build pre run logger -var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => -{ - configuration.Console.Enable = true; - configuration.Console.EnableAnsiMode = true; - configuration.FileLogging.Enable = false; -}); - -using var loggerFactory = new LoggerFactory(providers); -var logger = loggerFactory.CreateLogger("Startup"); - -// Fancy start console output... yes very fancy :> -Console.Write("Running "); - -var rainbow = new Crayon.Rainbow(0.5); -foreach (var c in "Moonlight") -{ - Console.Write( - rainbow - .Next() - .Bold() - .Text(c.ToString()) - ); -} - -Console.WriteLine(); - -// Building app -var builder = WebAssemblyHostBuilder.CreateDefault(args); - -// Configure application logging -builder.Logging.ClearProviders(); -builder.Logging.AddProviders(providers); - -builder.RootComponents.Add("#app"); -builder.RootComponents.Add("head::after"); - -builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - -builder.AddTokenAuthentication(); -builder.AddOAuth2(); - -/* -builder.Services.AddScoped(sp => -{ - var httpClient = sp.GetRequiredService(); - var localStorageService = sp.GetRequiredService(); - var result = new HttpApiClient(httpClient); - - result.AddLocalStorageTokenAuthentication(localStorageService, async refreshToken => - { - try - { - var httpApiClient = new HttpApiClient(httpClient); - - var response = await httpApiClient.PostJson( - "api/auth/refresh", - new RefreshRequest() - { - RefreshToken = refreshToken - } - ); - - return (new TokenPair() - { - AccessToken = response.AccessToken, - RefreshToken = response.RefreshToken - }, response.ExpiresAt); - } - catch (HttpApiException) - { - return (new TokenPair() - { - AccessToken = "unset", - RefreshToken = "unset" - }, DateTime.MinValue); - } - }); - - return result; -});*/ - -builder.Services.AddMoonCoreBlazorTailwind(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - -builder.Services.AutoAddServices(); - -FormComponentRepository.Set(); -FormComponentRepository.Set(); -FormComponentRepository.Set(); - -// Interface service -builder.Services.AddPlugins(configuration => -{ - configuration.AddAssembly(Assembly.GetEntryAssembly()!); - - configuration.AddInterface(); - configuration.AddInterface(); - - configuration.AddInterface(); -}); - -var app = builder.Build(); - -await app.RunAsync(); \ No newline at end of file +await Startup.Run(args, []); \ No newline at end of file diff --git a/Moonlight.Client/Startup.cs b/Moonlight.Client/Startup.cs new file mode 100644 index 00000000..cbff2642 --- /dev/null +++ b/Moonlight.Client/Startup.cs @@ -0,0 +1,131 @@ +using System.Reflection; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using MoonCore.Blazor.Extensions; +using MoonCore.Blazor.Services; +using MoonCore.Blazor.Tailwind.Extensions; +using MoonCore.Blazor.Tailwind.Forms; +using MoonCore.Blazor.Tailwind.Forms.Components; +using MoonCore.Extensions; +using MoonCore.Helpers; +using MoonCore.PluginFramework.Extensions; +using Moonlight.Client.Interfaces; +using Moonlight.Client.Services; +using Moonlight.Client.UI; +using Moonlight.Client.UI.Forms; + +namespace Moonlight.Client; + +public class Startup +{ + public static async Task Run(string[] args, Assembly[] assemblies) + { + // Build pre run logger + var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => + { + configuration.Console.Enable = true; + configuration.Console.EnableAnsiMode = true; + configuration.FileLogging.Enable = false; + }); + + using var loggerFactory = new LoggerFactory(providers); + var logger = loggerFactory.CreateLogger("Startup"); + + // Fancy start console output... yes very fancy :> + Console.Write("Running "); + + var rainbow = new Crayon.Rainbow(0.5); + foreach (var c in "Moonlight") + { + Console.Write( + rainbow + .Next() + .Bold() + .Text(c.ToString()) + ); + } + + Console.WriteLine(); + + // Building app + var builder = WebAssemblyHostBuilder.CreateDefault(args); + + // Configure application logging + builder.Logging.ClearProviders(); + builder.Logging.AddProviders(providers); + + builder.RootComponents.Add("#app"); + builder.RootComponents.Add("head::after"); + + builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + + builder.AddTokenAuthentication(); + builder.AddOAuth2(); + +/* +builder.Services.AddScoped(sp => +{ + var httpClient = sp.GetRequiredService(); + var localStorageService = sp.GetRequiredService(); + var result = new HttpApiClient(httpClient); + + result.AddLocalStorageTokenAuthentication(localStorageService, async refreshToken => + { + try + { + var httpApiClient = new HttpApiClient(httpClient); + + var response = await httpApiClient.PostJson( + "api/auth/refresh", + new RefreshRequest() + { + RefreshToken = refreshToken + } + ); + + return (new TokenPair() + { + AccessToken = response.AccessToken, + RefreshToken = response.RefreshToken + }, response.ExpiresAt); + } + catch (HttpApiException) + { + return (new TokenPair() + { + AccessToken = "unset", + RefreshToken = "unset" + }, DateTime.MinValue); + } + }); + + return result; +});*/ + + builder.Services.AddMoonCoreBlazorTailwind(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + builder.Services.AutoAddServices(); + + FormComponentRepository.Set(); + FormComponentRepository.Set(); + FormComponentRepository.Set(); + + // Interface service + builder.Services.AddPlugins(configuration => + { + configuration.AddAssembly(typeof(Startup).Assembly); + configuration.AddAssemblies(assemblies); + + configuration.AddInterface(); + configuration.AddInterface(); + + configuration.AddInterface(); + }); + + var app = builder.Build(); + + await app.RunAsync(); + } +} \ No newline at end of file diff --git a/Moonlight.Shared/Moonlight.Shared.csproj b/Moonlight.Shared/Moonlight.Shared.csproj index 3a635329..7c1cbe57 100644 --- a/Moonlight.Shared/Moonlight.Shared.csproj +++ b/Moonlight.Shared/Moonlight.Shared.csproj @@ -4,6 +4,17 @@ net8.0 enable enable + + true + 2.1.0 + Moonlight.Shared + Moonlight Panel + A build of moonlight's shared classes as a nuget package to develop moonlight plugins/modules + Moonlight Panel + https://github.com/Moonlight-Panel/Moonlight + https://github.com/Moonlight-Panel/Moonlight + git + moonlight