Merge branch 'v2_ChangeArchitecture' into v2_ChangeArchitecture_AddDiagnose
This commit is contained in:
@@ -44,6 +44,8 @@ public class AppConfiguration
|
|||||||
public string? AuthorizationEndpoint { get; set; }
|
public string? AuthorizationEndpoint { get; set; }
|
||||||
public string? AccessEndpoint { get; set; }
|
public string? AccessEndpoint { get; set; }
|
||||||
public string? AuthorizationRedirect { get; set; }
|
public string? AuthorizationRedirect { get; set; }
|
||||||
|
|
||||||
|
public bool FirstUserAdmin { get; set; } = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,5 +57,6 @@ public class AppConfiguration
|
|||||||
public class KestrelConfig
|
public class KestrelConfig
|
||||||
{
|
{
|
||||||
public int UploadLimit { get; set; } = 100;
|
public int UploadLimit { get; set; } = 100;
|
||||||
|
public string AllowedOrigins { get; set; } = "*";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
# Prepare runtime docker image
|
#
|
||||||
|
# OUTDATED
|
||||||
|
# Use https://github.com/Moonlight-Panel/Deploy
|
||||||
|
#
|
||||||
|
|
||||||
|
# Prepare runtime docker image
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled AS base
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
using Microsoft.Extensions.FileProviders.Physical;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
using MoonCore.Helpers;
|
|
||||||
|
|
||||||
namespace Moonlight.ApiServer.Helpers;
|
|
||||||
|
|
||||||
public class BundleAssetFileProvider : IFileProvider
|
|
||||||
{
|
|
||||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
|
||||||
=> NotFoundDirectoryContents.Singleton;
|
|
||||||
|
|
||||||
public IFileInfo GetFileInfo(string subpath)
|
|
||||||
{
|
|
||||||
if(subpath != "/css/bundle.css")
|
|
||||||
return new NotFoundFileInfo(subpath);
|
|
||||||
|
|
||||||
var physicalPath = PathBuilder.File("storage", "tmp", "bundle.css");
|
|
||||||
|
|
||||||
if(!File.Exists(physicalPath))
|
|
||||||
return new NotFoundFileInfo(subpath);
|
|
||||||
|
|
||||||
return new PhysicalFileInfo(new FileInfo(physicalPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IChangeToken Watch(string filter)
|
|
||||||
=> NullChangeToken.Singleton;
|
|
||||||
}
|
|
||||||
@@ -12,31 +12,13 @@ namespace Moonlight.ApiServer.Http.Controllers;
|
|||||||
public class FrontendController : Controller
|
public class FrontendController : Controller
|
||||||
{
|
{
|
||||||
private readonly FrontendService FrontendService;
|
private readonly FrontendService FrontendService;
|
||||||
private readonly PluginService PluginService;
|
|
||||||
|
|
||||||
public FrontendController(FrontendService frontendService, PluginService pluginService)
|
public FrontendController(FrontendService frontendService)
|
||||||
{
|
{
|
||||||
FrontendService = frontendService;
|
FrontendService = frontendService;
|
||||||
PluginService = pluginService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("frontend.json")]
|
[HttpGet("frontend.json")]
|
||||||
public async Task<FrontendConfiguration> GetConfiguration()
|
public async Task<FrontendConfiguration> GetConfiguration()
|
||||||
=> await FrontendService.GetConfiguration();
|
=> await FrontendService.GetConfiguration();
|
||||||
|
|
||||||
[HttpGet("plugins/{assemblyName}")]
|
|
||||||
public async Task GetPluginAssembly(string assemblyName)
|
|
||||||
{
|
|
||||||
var assembliesMap = PluginService.GetAssemblies("client");
|
|
||||||
|
|
||||||
if (!assembliesMap.TryGetValue(assemblyName, out var path))
|
|
||||||
throw new HttpApiException("The requested assembly could not be found", 404);
|
|
||||||
|
|
||||||
var absolutePath = Path.Combine(
|
|
||||||
Directory.GetCurrentDirectory(),
|
|
||||||
path
|
|
||||||
);
|
|
||||||
|
|
||||||
await Results.File(absolutePath).ExecuteAsync(HttpContext);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -273,13 +273,22 @@ public class OAuth2Controller : Controller
|
|||||||
|
|
||||||
if (await UserRepository.Get().AnyAsync(x => x.Email == email))
|
if (await UserRepository.Get().AnyAsync(x => x.Email == email))
|
||||||
throw new HttpApiException("A account with that email already exists", 400);
|
throw new HttpApiException("A account with that email already exists", 400);
|
||||||
|
|
||||||
var user = new User()
|
var user = new User()
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
Email = email,
|
Email = email,
|
||||||
Password = HashHelper.Hash(password)
|
Password = HashHelper.Hash(password),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Configuration.Authentication.OAuth2.FirstUserAdmin)
|
||||||
|
{
|
||||||
|
var userCount = await UserRepository.Get().CountAsync();
|
||||||
|
|
||||||
|
if (userCount == 0)
|
||||||
|
user.PermissionsJson = "[\"*\"]";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return await UserRepository.Add(user);
|
return await UserRepository.Add(user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<form class="space-y-6" method="POST">
|
<form class="space-y-6" method="POST">
|
||||||
<div>
|
<div>
|
||||||
<label for="email" class="block text-sm font-medium leading-6 text-gray-100">Username</label>
|
<label for="username" class="block text-sm font-medium leading-6 text-gray-100">Username</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input id="username" name="username" type="text" required class="block bg-white/5 w-full rounded-md border-0 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-700 placeholder:text-gray-600 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6">
|
<input id="username" name="username" type="text" required class="block bg-white/5 w-full rounded-md border-0 py-1.5 text-gray-100 shadow-sm ring-1 ring-inset ring-gray-700 placeholder:text-gray-600 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,24 +3,20 @@ using Moonlight.ApiServer.Configuration;
|
|||||||
using Moonlight.ApiServer.Database;
|
using Moonlight.ApiServer.Database;
|
||||||
using Moonlight.ApiServer.Implementations.Diagnose;
|
using Moonlight.ApiServer.Implementations.Diagnose;
|
||||||
using Moonlight.ApiServer.Interfaces;
|
using Moonlight.ApiServer.Interfaces;
|
||||||
using Moonlight.ApiServer.Interfaces.Startup;
|
using Moonlight.ApiServer.Plugins;
|
||||||
|
|
||||||
namespace Moonlight.ApiServer.Implementations.Startup;
|
namespace Moonlight.ApiServer.Implementations.Startup;
|
||||||
|
|
||||||
|
[PluginStartup]
|
||||||
public class CoreStartup : IPluginStartup
|
public class CoreStartup : IPluginStartup
|
||||||
{
|
{
|
||||||
private readonly AppConfiguration Configuration;
|
public Task BuildApplication(IServiceProvider serviceProvider, IHostApplicationBuilder builder)
|
||||||
|
|
||||||
public CoreStartup(AppConfiguration configuration)
|
|
||||||
{
|
|
||||||
Configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task BuildApplication(IHostApplicationBuilder builder)
|
|
||||||
{
|
{
|
||||||
|
var configuration = serviceProvider.GetRequiredService<AppConfiguration>();
|
||||||
|
|
||||||
#region Api Docs
|
#region Api Docs
|
||||||
|
|
||||||
if (Configuration.Development.EnableApiDocs)
|
if (configuration.Development.EnableApiDocs)
|
||||||
{
|
{
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
|
||||||
@@ -62,14 +58,16 @@ public class CoreStartup : IPluginStartup
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ConfigureApplication(IApplicationBuilder app)
|
public Task ConfigureApplication(IServiceProvider serviceProvider, IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ConfigureEndpoints(IEndpointRouteBuilder routeBuilder)
|
public Task ConfigureEndpoints(IServiceProvider serviceProvider, IEndpointRouteBuilder routeBuilder)
|
||||||
{
|
{
|
||||||
if(Configuration.Development.EnableApiDocs)
|
var configuration = serviceProvider.GetRequiredService<AppConfiguration>();
|
||||||
|
|
||||||
|
if(configuration.Development.EnableApiDocs)
|
||||||
routeBuilder.MapSwagger("/api/swagger/{documentName}");
|
routeBuilder.MapSwagger("/api/swagger/{documentName}");
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Moonlight.ApiServer.Interfaces.Startup;
|
|
||||||
|
|
||||||
public interface IPluginStartup
|
|
||||||
{
|
|
||||||
public Task BuildApplication(IHostApplicationBuilder builder);
|
|
||||||
public Task ConfigureApplication(IApplicationBuilder app);
|
|
||||||
public Task ConfigureEndpoints(IEndpointRouteBuilder routeBuilder);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
|
public class FrontendConfigurationOption
|
||||||
|
{
|
||||||
|
public string[] Scripts { get; set; } = [];
|
||||||
|
public string[] Styles { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
namespace Moonlight.ApiServer.Models;
|
|
||||||
|
|
||||||
public class PluginManifest
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Author { get; set; }
|
|
||||||
public string[] Dependencies { get; set; } = [];
|
|
||||||
|
|
||||||
public string[] Scripts { get; set; } = [];
|
|
||||||
public string[] Styles { get; set; } = [];
|
|
||||||
|
|
||||||
public Dictionary<string, string[]> Assemblies { get; set; } = new();
|
|
||||||
}
|
|
||||||
@@ -1,70 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
<PropertyGroup>
|
||||||
<PropertyGroup>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<Nullable>enable</Nullable>
|
||||||
<Nullable>enable</Nullable>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
</PropertyGroup>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<ItemGroup>
|
||||||
</PropertyGroup>
|
<ProjectReference Include="..\Moonlight.Client\Moonlight.Client.csproj" />
|
||||||
|
<ProjectReference Include="..\Moonlight.Shared\Moonlight.Shared.csproj" />
|
||||||
<ItemGroup>
|
</ItemGroup>
|
||||||
<ProjectReference Include="..\Moonlight.Client\Moonlight.Client.csproj"/>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Moonlight.Shared\Moonlight.Shared.csproj"/>
|
<Folder Include="Database\Migrations\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
<ItemGroup>
|
<Content Include="..\.dockerignore">
|
||||||
<Folder Include="Database\Migrations\"/>
|
<Link>.dockerignore</Link>
|
||||||
</ItemGroup>
|
<Pack>false</Pack>
|
||||||
|
</Content>
|
||||||
<ItemGroup>
|
</ItemGroup>
|
||||||
<Content Include="..\.dockerignore">
|
<PropertyGroup>
|
||||||
<Link>.dockerignore</Link>
|
<PackageId>Moonlight.ApiServer</PackageId>
|
||||||
<Pack>false</Pack>
|
<Version>2.1.0</Version>
|
||||||
</Content>
|
<Authors>Moonlight Panel</Authors>
|
||||||
</ItemGroup>
|
<Description>A build of the api server for moonlight development</Description>
|
||||||
|
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
||||||
|
<DevelopmentDependency>true</DevelopmentDependency>
|
||||||
<PropertyGroup>
|
<PackageTags>apiserver</PackageTags>
|
||||||
<PackageId>Moonlight.ApiServer</PackageId>
|
<IsPackable>true</IsPackable>
|
||||||
<Version>2.1.0</Version>
|
</PropertyGroup>
|
||||||
<Authors>Moonlight Panel</Authors>
|
<ItemGroup>
|
||||||
<Description>A build of the api server for moonlight development</Description>
|
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.18" />
|
||||||
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
<PackageReference Include="Hangfire.Core" Version="1.8.18" />
|
||||||
<DevelopmentDependency>true</DevelopmentDependency>
|
<PackageReference Include="Hangfire.EntityFrameworkCore" Version="0.7.0" />
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.5" />
|
||||||
<IsPackable>true</IsPackable>
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5" />
|
||||||
</PropertyGroup>
|
<PackageReference Include="MoonCore" Version="1.8.6" />
|
||||||
|
<PackageReference Include="MoonCore.Extended" Version="1.3.3" />
|
||||||
<ItemGroup>
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageReference Include="ExCSS" Version="4.3.0"/>
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.18"/>
|
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||||
<PackageReference Include="Hangfire.Core" Version="1.8.18"/>
|
</ItemGroup>
|
||||||
<PackageReference Include="Hangfire.EntityFrameworkCore" Version="0.7.0"/>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.10"/>
|
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8"/>
|
<Pack>true</Pack>
|
||||||
<PackageReference Include="MoonCore" Version="1.8.5"/>
|
<PackagePath>src</PackagePath>
|
||||||
<PackageReference Include="MoonCore.Extended" Version="1.3.2"/>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.5"/>
|
</None>
|
||||||
<PackageReference Include="SharpZipLib" Version="1.4.2"/>
|
<None Include="**\*.razor" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
<Pack>true</Pack>
|
||||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1"/>
|
<PackagePath>src</PackagePath>
|
||||||
</ItemGroup>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<ItemGroup>
|
<Compile Remove="storage\**\*" />
|
||||||
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
<Content Remove="storage\**\*" />
|
||||||
<Pack>true</Pack>
|
<None Remove="storage\**\*" />
|
||||||
<PackagePath>src</PackagePath>
|
</ItemGroup>
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
</Project>
|
||||||
</None>
|
|
||||||
<None Include="**\*.razor" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
|
||||||
<Pack>true</Pack>
|
|
||||||
<PackagePath>src</PackagePath>
|
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<Compile Remove="storage\**\*" />
|
|
||||||
<Content Remove="storage\**\*" />
|
|
||||||
<None Remove="storage\**\*" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
8
Moonlight.ApiServer/Plugins/IPluginStartup.cs
Normal file
8
Moonlight.ApiServer/Plugins/IPluginStartup.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Moonlight.ApiServer.Plugins;
|
||||||
|
|
||||||
|
public interface IPluginStartup
|
||||||
|
{
|
||||||
|
public Task BuildApplication(IServiceProvider serviceProvider, IHostApplicationBuilder builder);
|
||||||
|
public Task ConfigureApplication(IServiceProvider serviceProvider, IApplicationBuilder app);
|
||||||
|
public Task ConfigureEndpoints(IServiceProvider serviceProvider, IEndpointRouteBuilder routeBuilder);
|
||||||
|
}
|
||||||
7
Moonlight.ApiServer/Plugins/PluginStartupAttribute.cs
Normal file
7
Moonlight.ApiServer/Plugins/PluginStartupAttribute.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.ApiServer.Plugins;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class PluginStartupAttribute : Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ using MoonCore.Attributes;
|
|||||||
using MoonCore.Exceptions;
|
using MoonCore.Exceptions;
|
||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
using Moonlight.ApiServer.Configuration;
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Models;
|
||||||
using Moonlight.Shared.Misc;
|
using Moonlight.Shared.Misc;
|
||||||
|
|
||||||
namespace Moonlight.ApiServer.Services;
|
namespace Moonlight.ApiServer.Services;
|
||||||
@@ -14,18 +15,18 @@ namespace Moonlight.ApiServer.Services;
|
|||||||
public class FrontendService
|
public class FrontendService
|
||||||
{
|
{
|
||||||
private readonly AppConfiguration Configuration;
|
private readonly AppConfiguration Configuration;
|
||||||
private readonly PluginService PluginService;
|
|
||||||
private readonly IWebHostEnvironment WebHostEnvironment;
|
private readonly IWebHostEnvironment WebHostEnvironment;
|
||||||
|
private readonly IEnumerable<FrontendConfigurationOption> ConfigurationOptions;
|
||||||
|
|
||||||
public FrontendService(
|
public FrontendService(
|
||||||
AppConfiguration configuration,
|
AppConfiguration configuration,
|
||||||
PluginService pluginService,
|
IWebHostEnvironment webHostEnvironment,
|
||||||
IWebHostEnvironment webHostEnvironment
|
IEnumerable<FrontendConfigurationOption> configurationOptions
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Configuration = configuration;
|
Configuration = configuration;
|
||||||
PluginService = pluginService;
|
|
||||||
WebHostEnvironment = webHostEnvironment;
|
WebHostEnvironment = webHostEnvironment;
|
||||||
|
ConfigurationOptions = configurationOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FrontendConfiguration> GetConfiguration()
|
public async Task<FrontendConfiguration> GetConfiguration()
|
||||||
@@ -48,33 +49,15 @@ public class FrontendService
|
|||||||
.Deserialize<Dictionary<string, string>>(variablesJson) ?? new();
|
.Deserialize<Dictionary<string, string>>(variablesJson) ?? new();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect assemblies for the 'client' section
|
|
||||||
configuration.Assemblies = PluginService
|
|
||||||
.GetAssemblies("client")
|
|
||||||
.Keys
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
// Collect scripts to execute
|
// Collect scripts to execute
|
||||||
configuration.Scripts = PluginService
|
configuration.Scripts = ConfigurationOptions
|
||||||
.LoadedPlugins
|
|
||||||
.Keys
|
|
||||||
.SelectMany(x => x.Scripts)
|
.SelectMany(x => x.Scripts)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// Collect styles
|
// Collect styles
|
||||||
var styles = new List<string>();
|
configuration.Styles = ConfigurationOptions
|
||||||
|
.SelectMany(x => x.Styles)
|
||||||
styles.AddRange(
|
.ToArray();
|
||||||
PluginService
|
|
||||||
.LoadedPlugins
|
|
||||||
.Keys
|
|
||||||
.SelectMany(x => x.Styles)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add bundle css
|
|
||||||
styles.Add("css/bundle.min.css");
|
|
||||||
|
|
||||||
configuration.Styles = styles.ToArray();
|
|
||||||
|
|
||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
@@ -111,42 +94,12 @@ public class FrontendService
|
|||||||
// Add blazor files
|
// Add blazor files
|
||||||
await ArchiveFsItem(zipArchive, blazorPath, blazorPath, "_framework/");
|
await ArchiveFsItem(zipArchive, blazorPath, blazorPath, "_framework/");
|
||||||
|
|
||||||
// Add bundle.css
|
|
||||||
var bundleContent = await File.ReadAllBytesAsync(Path.Combine("storage", "tmp", "bundle.css"));
|
|
||||||
await ArchiveBytes(zipArchive, "css/bundle.css", bundleContent);
|
|
||||||
|
|
||||||
// Add frontend.json
|
// Add frontend.json
|
||||||
var frontendConfig = await GetConfiguration();
|
var frontendConfig = await GetConfiguration();
|
||||||
frontendConfig.HostEnvironment = "Static";
|
frontendConfig.HostEnvironment = "Static";
|
||||||
var frontendJson = JsonSerializer.Serialize(frontendConfig);
|
var frontendJson = JsonSerializer.Serialize(frontendConfig);
|
||||||
await ArchiveText(zipArchive, "frontend.json", frontendJson);
|
await ArchiveText(zipArchive, "frontend.json", frontendJson);
|
||||||
|
|
||||||
// Add plugin wwwroot files
|
|
||||||
foreach (var pluginPath in PluginService.LoadedPlugins.Values)
|
|
||||||
{
|
|
||||||
var wwwRootPluginPath = Path.Combine(pluginPath, "wwwroot/");
|
|
||||||
|
|
||||||
if (!Directory.Exists(wwwRootPluginPath))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
await ArchiveFsItem(zipArchive, wwwRootPluginPath, wwwRootPluginPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add plugin assemblies for client to the zip file
|
|
||||||
var assembliesMap = PluginService.GetAssemblies("client");
|
|
||||||
|
|
||||||
foreach (var assemblyName in assembliesMap.Keys)
|
|
||||||
{
|
|
||||||
var path = assembliesMap[assemblyName];
|
|
||||||
|
|
||||||
await ArchiveFsItem(
|
|
||||||
zipArchive,
|
|
||||||
path,
|
|
||||||
path,
|
|
||||||
$"plugins/{assemblyName}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish zip archive and reset stream so the code calling this function can process it
|
// Finish zip archive and reset stream so the code calling this function can process it
|
||||||
zipArchive.Dispose();
|
zipArchive.Dispose();
|
||||||
await memoryStream.FlushAsync();
|
await memoryStream.FlushAsync();
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
using MoonCore.Helpers;
|
|
||||||
using Moonlight.ApiServer.Models;
|
|
||||||
|
|
||||||
namespace Moonlight.ApiServer.Services;
|
|
||||||
|
|
||||||
public class PluginService
|
|
||||||
{
|
|
||||||
private readonly ILogger<PluginService> Logger;
|
|
||||||
private readonly string PluginRoot;
|
|
||||||
|
|
||||||
public readonly Dictionary<PluginManifest, string> LoadedPlugins = new();
|
|
||||||
public IFileProvider WwwRootFileProvider;
|
|
||||||
|
|
||||||
public PluginService(ILogger<PluginService> logger)
|
|
||||||
{
|
|
||||||
Logger = logger;
|
|
||||||
|
|
||||||
PluginRoot = PathBuilder.Dir("storage", "plugins");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Load()
|
|
||||||
{
|
|
||||||
var jsonOptions = new JsonSerializerOptions()
|
|
||||||
{
|
|
||||||
PropertyNameCaseInsensitive = true
|
|
||||||
};
|
|
||||||
|
|
||||||
var pluginDirs = Directory.GetDirectories(PluginRoot);
|
|
||||||
var pluginMap = new Dictionary<PluginManifest, string>();
|
|
||||||
|
|
||||||
#region Scan plugins/ directory for plugin.json files
|
|
||||||
|
|
||||||
foreach (var dir in pluginDirs)
|
|
||||||
{
|
|
||||||
var metaPath = PathBuilder.File(dir, "plugin.json");
|
|
||||||
|
|
||||||
if (!File.Exists(metaPath))
|
|
||||||
{
|
|
||||||
Logger.LogWarning("Skipped '{dir}' as it is missing a plugin.json", dir);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var json = await File.ReadAllTextAsync(metaPath);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var meta = JsonSerializer.Deserialize<PluginManifest>(json, jsonOptions);
|
|
||||||
|
|
||||||
if (meta == null)
|
|
||||||
throw new JsonException("Unable to parse. Return value was null");
|
|
||||||
|
|
||||||
pluginMap.Add(meta, dir);
|
|
||||||
}
|
|
||||||
catch (JsonException e)
|
|
||||||
{
|
|
||||||
Logger.LogError("Unable to load plugin.json at '{path}': {e}", metaPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Depdenency check
|
|
||||||
|
|
||||||
foreach (var plugin in pluginMap.Keys)
|
|
||||||
{
|
|
||||||
var hasMissingDep = false;
|
|
||||||
|
|
||||||
foreach (var dependency in plugin.Dependencies)
|
|
||||||
{
|
|
||||||
if (pluginMap.Keys.All(x => x.Id != dependency))
|
|
||||||
{
|
|
||||||
hasMissingDep = true;
|
|
||||||
Logger.LogWarning("Plugin '{name}' has missing dependency: {dep}", plugin.Name, dependency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasMissingDep)
|
|
||||||
Logger.LogWarning("Unable to load '{name}' due to missing dependencies", plugin.Name);
|
|
||||||
else
|
|
||||||
LoadedPlugins.Add(plugin, pluginMap[plugin]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Create wwwroot file provider
|
|
||||||
|
|
||||||
Logger.LogInformation("Creating wwwroot file provider");
|
|
||||||
WwwRootFileProvider = CreateWwwRootProvider();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
Logger.LogInformation("Loaded {count} plugins", LoadedPlugins.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<string, string> GetAssemblies(string section)
|
|
||||||
{
|
|
||||||
var assemblyMap = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
foreach (var loadedPlugin in LoadedPlugins.Keys)
|
|
||||||
{
|
|
||||||
// Skip all plugins which haven't defined any assemblies in that section
|
|
||||||
if (!loadedPlugin.Assemblies.ContainsKey(section))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var pluginPath = LoadedPlugins[loadedPlugin];
|
|
||||||
|
|
||||||
foreach (var assembly in loadedPlugin.Assemblies[section])
|
|
||||||
{
|
|
||||||
var assemblyFile = Path.GetFileName(assembly);
|
|
||||||
assemblyMap[assemblyFile] = PathBuilder.File(pluginPath, assembly);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return assemblyMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IFileProvider CreateWwwRootProvider()
|
|
||||||
{
|
|
||||||
List<IFileProvider> wwwRootProviders = new();
|
|
||||||
|
|
||||||
foreach (var pluginFolder in LoadedPlugins.Values)
|
|
||||||
{
|
|
||||||
var wwwRootPath = Path.GetFullPath(
|
|
||||||
PathBuilder.Dir(pluginFolder, "wwwroot")
|
|
||||||
);
|
|
||||||
|
|
||||||
if(!Directory.Exists(wwwRootPath))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
wwwRootProviders.Add(
|
|
||||||
new PhysicalFileProvider(wwwRootPath)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CompositeFileProvider(wwwRootProviders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
using System.Reflection;
|
|
||||||
using System.Runtime.Loader;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Hangfire.EntityFrameworkCore;
|
using Hangfire.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using MoonCore.Configuration;
|
|
||||||
using MoonCore.EnvConfiguration;
|
using MoonCore.EnvConfiguration;
|
||||||
using MoonCore.Extended.Abstractions;
|
using MoonCore.Extended.Abstractions;
|
||||||
using MoonCore.Extended.Extensions;
|
using MoonCore.Extended.Extensions;
|
||||||
@@ -15,16 +12,13 @@ using MoonCore.Extended.Helpers;
|
|||||||
using MoonCore.Extended.JwtInvalidation;
|
using MoonCore.Extended.JwtInvalidation;
|
||||||
using MoonCore.Extensions;
|
using MoonCore.Extensions;
|
||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
using MoonCore.Services;
|
|
||||||
using Moonlight.ApiServer.Configuration;
|
using Moonlight.ApiServer.Configuration;
|
||||||
using Moonlight.ApiServer.Database;
|
using Moonlight.ApiServer.Database;
|
||||||
using Moonlight.ApiServer.Database.Entities;
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
using Moonlight.ApiServer.Helpers;
|
|
||||||
using Moonlight.ApiServer.Implementations;
|
using Moonlight.ApiServer.Implementations;
|
||||||
|
using Moonlight.ApiServer.Implementations.Startup;
|
||||||
using Moonlight.ApiServer.Interfaces;
|
using Moonlight.ApiServer.Interfaces;
|
||||||
using Moonlight.ApiServer.Interfaces.Startup;
|
using Moonlight.ApiServer.Plugins;
|
||||||
using Moonlight.ApiServer.Models;
|
|
||||||
using Moonlight.ApiServer.Services;
|
|
||||||
|
|
||||||
namespace Moonlight.ApiServer;
|
namespace Moonlight.ApiServer;
|
||||||
|
|
||||||
@@ -34,8 +28,6 @@ namespace Moonlight.ApiServer;
|
|||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
private string[] Args;
|
private string[] Args;
|
||||||
private Assembly[] AdditionalAssemblies;
|
|
||||||
private PluginManifest[] AdditionalPluginManifests;
|
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
private ILoggerProvider[] LoggerProviders;
|
private ILoggerProvider[] LoggerProviders;
|
||||||
@@ -51,24 +43,20 @@ public class Startup
|
|||||||
private WebApplicationBuilder WebApplicationBuilder;
|
private WebApplicationBuilder WebApplicationBuilder;
|
||||||
|
|
||||||
// Plugin Loading
|
// Plugin Loading
|
||||||
private PluginService PluginService;
|
|
||||||
private AssemblyLoadContext PluginLoadContext;
|
|
||||||
|
|
||||||
private IPluginStartup[] PluginStartups;
|
private IPluginStartup[] PluginStartups;
|
||||||
|
private IPluginStartup[] AdditionalPlugins;
|
||||||
|
private IServiceProvider PluginLoadServiceProvider;
|
||||||
|
|
||||||
public async Task Run(string[] args, Assembly[]? additionalAssemblies = null,
|
public async Task Run(string[] args, IPluginStartup[]? additionalPlugins = null)
|
||||||
PluginManifest[]? additionalManifests = null)
|
|
||||||
{
|
{
|
||||||
Args = args;
|
Args = args;
|
||||||
AdditionalAssemblies = additionalAssemblies ?? [];
|
AdditionalPlugins = additionalPlugins ?? [];
|
||||||
AdditionalPluginManifests = additionalManifests ?? [];
|
|
||||||
|
|
||||||
await PrintVersion();
|
await PrintVersion();
|
||||||
|
|
||||||
await CreateStorage();
|
await CreateStorage();
|
||||||
await SetupAppConfiguration();
|
await SetupAppConfiguration();
|
||||||
await SetupLogging();
|
await SetupLogging();
|
||||||
await LoadPlugins();
|
|
||||||
await InitializePlugins();
|
await InitializePlugins();
|
||||||
|
|
||||||
await CreateWebApplicationBuilder();
|
await CreateWebApplicationBuilder();
|
||||||
@@ -89,7 +77,6 @@ public class Startup
|
|||||||
await PrepareDatabase();
|
await PrepareDatabase();
|
||||||
|
|
||||||
await UseCors();
|
await UseCors();
|
||||||
await UsePluginAssets(); // We need to move the plugin assets to the top to allow plugins to override content
|
|
||||||
await UseBase();
|
await UseBase();
|
||||||
await UseAuth();
|
await UseAuth();
|
||||||
await UseHangfire();
|
await UseHangfire();
|
||||||
@@ -140,17 +127,13 @@ public class Startup
|
|||||||
|
|
||||||
// Add pre-existing services
|
// Add pre-existing services
|
||||||
WebApplicationBuilder.Services.AddSingleton(Configuration);
|
WebApplicationBuilder.Services.AddSingleton(Configuration);
|
||||||
WebApplicationBuilder.Services.AddSingleton(PluginService);
|
|
||||||
|
|
||||||
// Configure controllers
|
// Configure controllers
|
||||||
var mvcBuilder = WebApplicationBuilder.Services.AddControllers();
|
var mvcBuilder = WebApplicationBuilder.Services.AddControllers();
|
||||||
|
|
||||||
// Add plugin and additional assemblies as application parts
|
// Add plugin assemblies as application parts
|
||||||
foreach (var pluginAssembly in PluginLoadContext.Assemblies)
|
foreach (var pluginStartup in PluginStartups.Select(x => x.GetType().Assembly).Distinct())
|
||||||
mvcBuilder.AddApplicationPart(pluginAssembly);
|
mvcBuilder.AddApplicationPart(pluginStartup.GetType().Assembly);
|
||||||
|
|
||||||
foreach (var additionalAssembly in AdditionalAssemblies)
|
|
||||||
mvcBuilder.AddApplicationPart(additionalAssembly);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -200,89 +183,33 @@ public class Startup
|
|||||||
|
|
||||||
#region Plugin Loading
|
#region Plugin Loading
|
||||||
|
|
||||||
private async Task LoadPlugins()
|
|
||||||
{
|
|
||||||
// Load plugins
|
|
||||||
PluginService = new PluginService(
|
|
||||||
LoggerFactory.CreateLogger<PluginService>()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add plugins manually if specified in the startup
|
|
||||||
foreach (var manifest in AdditionalPluginManifests)
|
|
||||||
PluginService.LoadedPlugins.Add(manifest, Directory.GetCurrentDirectory());
|
|
||||||
|
|
||||||
// Search and load all plugins
|
|
||||||
await PluginService.Load();
|
|
||||||
|
|
||||||
// Search up assemblies for the apiServer
|
|
||||||
var assemblyFiles = PluginService.GetAssemblies("apiServer")
|
|
||||||
.Values
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
// Create the load context and add assemblies
|
|
||||||
PluginLoadContext = new AssemblyLoadContext(null);
|
|
||||||
|
|
||||||
foreach (var assemblyFile in assemblyFiles)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
PluginLoadContext.LoadFromAssemblyPath(
|
|
||||||
Path.Combine(Directory.GetCurrentDirectory(), assemblyFile)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError("Unable to load plugin assembly '{assemblyFile}': {e}", assemblyFile, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task InitializePlugins()
|
private Task InitializePlugins()
|
||||||
{
|
{
|
||||||
// Define minimal service collection
|
// Create service provider for starting up
|
||||||
var startupSc = new ServiceCollection();
|
var serviceCollection = new ServiceCollection();
|
||||||
|
|
||||||
// Configure base services for initialisation
|
serviceCollection.AddSingleton(Configuration);
|
||||||
startupSc.AddSingleton(Configuration);
|
|
||||||
|
|
||||||
startupSc.AddLogging(builder =>
|
serviceCollection.AddLogging(builder =>
|
||||||
{
|
{
|
||||||
builder.ClearProviders();
|
builder.ClearProviders();
|
||||||
builder.AddProviders(LoggerProviders);
|
builder.AddProviders(LoggerProviders);
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
PluginLoadServiceProvider = serviceCollection.BuildServiceProvider();
|
||||||
var startupSp = startupSc.BuildServiceProvider();
|
|
||||||
|
|
||||||
// Initialize plugin startups
|
// Collect startups
|
||||||
var startups = new List<IPluginStartup>();
|
var pluginStartups = new List<IPluginStartup>();
|
||||||
var startupType = typeof(IPluginStartup);
|
|
||||||
|
|
||||||
var assembliesToScan = new List<Assembly>();
|
pluginStartups.Add(new CoreStartup());
|
||||||
|
|
||||||
assembliesToScan.Add(typeof(Startup).Assembly);
|
pluginStartups.AddRange(AdditionalPlugins); // Used by the development server
|
||||||
assembliesToScan.AddRange(PluginLoadContext.Assemblies);
|
|
||||||
assembliesToScan.AddRange(AdditionalAssemblies);
|
|
||||||
|
|
||||||
foreach (var pluginAssembly in assembliesToScan)
|
// Do NOT remove the following comment, as its used to place the plugin startup register calls
|
||||||
{
|
// MLBUILD_PLUGIN_STARTUP_HERE
|
||||||
var startupTypes = pluginAssembly
|
|
||||||
.ExportedTypes
|
|
||||||
.Where(x => !x.IsAbstract && !x.IsInterface && x.IsAssignableTo(startupType))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var type in startupTypes)
|
|
||||||
{
|
|
||||||
var startup = ActivatorUtilities.CreateInstance(startupSp, type) as IPluginStartup;
|
|
||||||
|
|
||||||
if (startup == null)
|
PluginStartups = pluginStartups.ToArray();
|
||||||
continue;
|
|
||||||
|
|
||||||
startups.Add(startup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginStartups = startups.ToArray();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -292,21 +219,6 @@ public class Startup
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task UsePluginAssets()
|
|
||||||
{
|
|
||||||
WebApplication.UseStaticFiles(new StaticFileOptions()
|
|
||||||
{
|
|
||||||
FileProvider = new BundleAssetFileProvider()
|
|
||||||
});
|
|
||||||
|
|
||||||
WebApplication.UseStaticFiles(new StaticFileOptions()
|
|
||||||
{
|
|
||||||
FileProvider = PluginService.WwwRootFileProvider
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Hooks
|
#region Hooks
|
||||||
|
|
||||||
private async Task HookPluginBuild()
|
private async Task HookPluginBuild()
|
||||||
@@ -315,7 +227,7 @@ public class Startup
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await pluginAppStartup.BuildApplication(WebApplicationBuilder);
|
await pluginAppStartup.BuildApplication(PluginLoadServiceProvider, WebApplicationBuilder);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -334,7 +246,7 @@ public class Startup
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await pluginAppStartup.ConfigureApplication(WebApplication);
|
await pluginAppStartup.ConfigureApplication(PluginLoadServiceProvider, WebApplication);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -353,7 +265,7 @@ public class Startup
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await pluginEndpointStartup.ConfigureEndpoints(WebApplication);
|
await pluginEndpointStartup.ConfigureEndpoints(PluginLoadServiceProvider, WebApplication);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -568,7 +480,7 @@ public class Startup
|
|||||||
});
|
});
|
||||||
|
|
||||||
WebApplicationBuilder.Services.AddAuthorization();
|
WebApplicationBuilder.Services.AddAuthorization();
|
||||||
|
|
||||||
// Add local oauth2 provider if enabled
|
// Add local oauth2 provider if enabled
|
||||||
if (Configuration.Authentication.EnableLocalOAuth2)
|
if (Configuration.Authentication.EnableLocalOAuth2)
|
||||||
WebApplicationBuilder.Services.AddScoped<IOAuth2Provider, LocalOAuth2Provider>();
|
WebApplicationBuilder.Services.AddScoped<IOAuth2Provider, LocalOAuth2Provider>();
|
||||||
@@ -593,12 +505,30 @@ public class Startup
|
|||||||
|
|
||||||
private Task RegisterCors()
|
private Task RegisterCors()
|
||||||
{
|
{
|
||||||
|
var allowedOrigins = Configuration.Kestrel.AllowedOrigins.Split(";", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
WebApplicationBuilder.Services.AddCors(options =>
|
WebApplicationBuilder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
options.AddDefaultPolicy(builder =>
|
var cors = new CorsPolicyBuilder();
|
||||||
|
|
||||||
|
if (allowedOrigins.Contains("*"))
|
||||||
{
|
{
|
||||||
builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().Build();
|
cors.SetIsOriginAllowed(_ => true)
|
||||||
});
|
.AllowAnyMethod()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowCredentials();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cors.WithOrigins(allowedOrigins)
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
options.AddDefaultPolicy(
|
||||||
|
cors.Build()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using Moonlight.Client.Interfaces;
|
using Moonlight.Client.Interfaces;
|
||||||
|
using Moonlight.Client.Plugins;
|
||||||
|
|
||||||
namespace Moonlight.Client.Implementations;
|
namespace Moonlight.Client.Implementations;
|
||||||
|
|
||||||
public class CoreStartup : IPluginStartup
|
public class CoreStartup : IPluginStartup
|
||||||
{
|
{
|
||||||
public Task BuildApplication(WebAssemblyHostBuilder builder)
|
public Task BuildApplication(IServiceProvider serviceProvider, WebAssemblyHostBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Services.AddSingleton<ISidebarItemProvider, DefaultSidebarItemProvider>();
|
builder.Services.AddSingleton<ISidebarItemProvider, DefaultSidebarItemProvider>();
|
||||||
builder.Services.AddSingleton<IOverviewElementProvider, DefaultOverviewElementProvider>();
|
builder.Services.AddSingleton<IOverviewElementProvider, DefaultOverviewElementProvider>();
|
||||||
@@ -13,6 +14,6 @@ public class CoreStartup : IPluginStartup
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ConfigureApplication(WebAssemblyHost app)
|
public Task ConfigureApplication(IServiceProvider serviceProvider, WebAssemblyHost app)
|
||||||
=> Task.CompletedTask;
|
=> Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
|
||||||
|
|
||||||
namespace Moonlight.Client.Interfaces;
|
|
||||||
|
|
||||||
public interface IPluginStartup
|
|
||||||
{
|
|
||||||
public Task BuildApplication(WebAssemblyHostBuilder builder);
|
|
||||||
public Task ConfigureApplication(WebAssemblyHost app);
|
|
||||||
}
|
|
||||||
@@ -1,71 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
<PropertyGroup>
|
||||||
<PropertyGroup>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<PublishTrimmed>false</PublishTrimmed>
|
<Nullable>enable</Nullable>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<DefaultItemExcludes>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
|
||||||
<DefaultItemExcludes>
|
|
||||||
**\bin\**;**\obj\**;**\node_modules\**;**\Styles\*.json
|
**\bin\**;**\obj\**;**\node_modules\**;**\Styles\*.json
|
||||||
</DefaultItemExcludes>
|
</DefaultItemExcludes>
|
||||||
<StaticWebAssetsEnabled>True</StaticWebAssetsEnabled>
|
<StaticWebAssetsEnabled>True</StaticWebAssetsEnabled>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<PackageTags>frontend</PackageTags>
|
||||||
<PropertyGroup>
|
<PackageId>Moonlight.Client</PackageId>
|
||||||
<PackageId>Moonlight.Client</PackageId>
|
<Version>2.1.0</Version>
|
||||||
<Version>2.1.0</Version>
|
<Authors>Moonlight Panel</Authors>
|
||||||
<Authors>Moonlight Panel</Authors>
|
<Description>A build of the client for moonlight development</Description>
|
||||||
<Description>A build of the client for moonlight development</Description>
|
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
||||||
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
<DevelopmentDependency>true</DevelopmentDependency>
|
||||||
<DevelopmentDependency>true</DevelopmentDependency>
|
<IsPackable>true</IsPackable>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<CompressionEnabled>false</CompressionEnabled>
|
||||||
<IsPackable>true</IsPackable>
|
</PropertyGroup>
|
||||||
</PropertyGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Blazor-ApexCharts" Version="6.0.0" />
|
||||||
<ItemGroup>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
|
||||||
<PackageReference Include="Blazor-ApexCharts" Version="4.0.1"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.5" PrivateAssets="all" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10"/>
|
<PackageReference Include="MoonCore" Version="1.8.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.10" PrivateAssets="all"/>
|
<PackageReference Include="MoonCore.Blazor" Version="1.3.0" />
|
||||||
<PackageReference Include="MoonCore" Version="1.8.5"/>
|
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.4.3" />
|
||||||
<PackageReference Include="MoonCore.Blazor" Version="1.2.9"/>
|
</ItemGroup>
|
||||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.5"/>
|
<ItemGroup>
|
||||||
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.4.2"/>
|
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
||||||
</ItemGroup>
|
<Pack>true</Pack>
|
||||||
|
<PackagePath>src</PackagePath>
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
<ItemGroup>
|
</None>
|
||||||
<None Include="**\*.cs" Exclude="storage\**\*;bin\**\*;obj\**\*">
|
<None Include="Styles\**\*" Exclude="storage\**\*;bin\**\*;obj\**\*;Styles\node_modules\**\*">
|
||||||
<Pack>true</Pack>
|
<Pack>true</Pack>
|
||||||
<PackagePath>src</PackagePath>
|
<PackagePath>styles</PackagePath>
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Include="Styles\**\*" Exclude="storage\**\*;bin\**\*;obj\**\*;Styles\node_modules\**\*">
|
<Compile Remove="storage\**\*" />
|
||||||
<Pack>true</Pack>
|
<Content Remove="storage\**\*" />
|
||||||
<PackagePath>styles</PackagePath>
|
<None Remove="storage\**\*" />
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
</ItemGroup>
|
||||||
</None>
|
<!--
|
||||||
<Compile Remove="storage\**\*"/>
|
Specify the /p:BuildPWA=true flag to build moonlight as a PWA.
|
||||||
<Content Remove="storage\**\*"/>
|
This flag is by default disabled to allow nuget package generation
|
||||||
<None Remove="storage\**\*"/>
|
-->
|
||||||
</ItemGroup>
|
<PropertyGroup Condition="'$(BuildPWA)' == 'true'">
|
||||||
|
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
|
||||||
<!--
|
</PropertyGroup>
|
||||||
Specify the /p:BuildPWA=true flag to build moonlight as a PWA.
|
<ItemGroup Condition="'$(BuildPWA)' == 'true'">
|
||||||
This flag is by default disabled to allow nuget package generation
|
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
|
||||||
-->
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
<PropertyGroup Condition="'$(BuildPWA)' == 'true'">
|
<ProjectReference Include="..\Moonlight.Shared\Moonlight.Shared.csproj" />
|
||||||
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
|
</ItemGroup>
|
||||||
</PropertyGroup>
|
</Project>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(BuildPWA)' == 'true'">
|
|
||||||
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Moonlight.Shared\Moonlight.Shared.csproj"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
9
Moonlight.Client/Plugins/IPluginStartup.cs
Normal file
9
Moonlight.Client/Plugins/IPluginStartup.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
|
|
||||||
|
namespace Moonlight.Client.Plugins;
|
||||||
|
|
||||||
|
public interface IPluginStartup
|
||||||
|
{
|
||||||
|
public Task BuildApplication(IServiceProvider serviceProvider, WebAssemblyHostBuilder builder);
|
||||||
|
public Task ConfigureApplication(IServiceProvider serviceProvider, WebAssemblyHost app);
|
||||||
|
}
|
||||||
7
Moonlight.Client/Plugins/PluginStartupAttribute.cs
Normal file
7
Moonlight.Client/Plugins/PluginStartupAttribute.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.Client.Plugins;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class PluginStartupAttribute : Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,7 +9,9 @@ using MoonCore.Blazor.Tailwind.Extensions;
|
|||||||
using MoonCore.Blazor.Tailwind.Auth;
|
using MoonCore.Blazor.Tailwind.Auth;
|
||||||
using MoonCore.Extensions;
|
using MoonCore.Extensions;
|
||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
|
using Moonlight.Client.Implementations;
|
||||||
using Moonlight.Client.Interfaces;
|
using Moonlight.Client.Interfaces;
|
||||||
|
using Moonlight.Client.Plugins;
|
||||||
using Moonlight.Client.Services;
|
using Moonlight.Client.Services;
|
||||||
using Moonlight.Shared.Misc;
|
using Moonlight.Shared.Misc;
|
||||||
using Moonlight.Client.UI;
|
using Moonlight.Client.UI;
|
||||||
@@ -33,15 +35,14 @@ public class Startup
|
|||||||
private WebAssemblyHost WebAssemblyHost;
|
private WebAssemblyHost WebAssemblyHost;
|
||||||
|
|
||||||
// Plugin Loading
|
// Plugin Loading
|
||||||
private AssemblyLoadContext PluginLoadContext;
|
private IPluginStartup[] AdditionalPlugins;
|
||||||
private Assembly[] AdditionalAssemblies;
|
|
||||||
|
|
||||||
private IPluginStartup[] PluginStartups;
|
private IPluginStartup[] PluginStartups;
|
||||||
|
private IServiceProvider PluginLoadServiceProvider;
|
||||||
|
|
||||||
public async Task Run(string[] args, Assembly[]? additionalAssemblies = null)
|
public async Task Run(string[] args, IPluginStartup[]? additionalPlugins = null)
|
||||||
{
|
{
|
||||||
Args = args;
|
Args = args;
|
||||||
AdditionalAssemblies = additionalAssemblies ?? [];
|
AdditionalPlugins = additionalPlugins ?? [];
|
||||||
|
|
||||||
await PrintVersion();
|
await PrintVersion();
|
||||||
await SetupLogging();
|
await SetupLogging();
|
||||||
@@ -49,7 +50,6 @@ public class Startup
|
|||||||
await CreateWebAssemblyHostBuilder();
|
await CreateWebAssemblyHostBuilder();
|
||||||
|
|
||||||
await LoadConfiguration();
|
await LoadConfiguration();
|
||||||
await LoadPlugins();
|
|
||||||
await InitializePlugins();
|
await InitializePlugins();
|
||||||
|
|
||||||
await RegisterLogging();
|
await RegisterLogging();
|
||||||
@@ -94,7 +94,7 @@ public class Startup
|
|||||||
httpClient.BaseAddress = new Uri(WebAssemblyHostBuilder.HostEnvironment.BaseAddress);
|
httpClient.BaseAddress = new Uri(WebAssemblyHostBuilder.HostEnvironment.BaseAddress);
|
||||||
|
|
||||||
var jsonText = await httpClient.GetStringAsync("frontend.json");
|
var jsonText = await httpClient.GetStringAsync("frontend.json");
|
||||||
|
|
||||||
Configuration = JsonSerializer.Deserialize<FrontendConfiguration>(jsonText, new JsonSerializerOptions()
|
Configuration = JsonSerializer.Deserialize<FrontendConfiguration>(jsonText, new JsonSerializerOptions()
|
||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true
|
PropertyNameCaseInsensitive = true
|
||||||
@@ -120,7 +120,7 @@ public class Startup
|
|||||||
BaseAddress = new Uri(Configuration.ApiUrl)
|
BaseAddress = new Uri(Configuration.ApiUrl)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
WebAssemblyHostBuilder.Services.AddScoped(sp =>
|
WebAssemblyHostBuilder.Services.AddScoped(sp =>
|
||||||
{
|
{
|
||||||
var httpClient = sp.GetRequiredService<HttpClient>();
|
var httpClient = sp.GetRequiredService<HttpClient>();
|
||||||
@@ -146,7 +146,7 @@ public class Startup
|
|||||||
WebAssemblyHostBuilder.Services.AddScoped<LocalStorageService>();
|
WebAssemblyHostBuilder.Services.AddScoped<LocalStorageService>();
|
||||||
|
|
||||||
WebAssemblyHostBuilder.Services.AddScoped<ThemeService>();
|
WebAssemblyHostBuilder.Services.AddScoped<ThemeService>();
|
||||||
|
|
||||||
WebAssemblyHostBuilder.Services.AutoAddServices<Program>();
|
WebAssemblyHostBuilder.Services.AutoAddServices<Program>();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -160,39 +160,15 @@ public class Startup
|
|||||||
|
|
||||||
foreach (var scriptName in Configuration.Scripts)
|
foreach (var scriptName in Configuration.Scripts)
|
||||||
await jsRuntime.InvokeVoidAsync("moonlight.assets.loadJavascript", scriptName);
|
await jsRuntime.InvokeVoidAsync("moonlight.assets.loadJavascript", scriptName);
|
||||||
|
|
||||||
|
foreach (var styleName in Configuration.Styles)
|
||||||
|
await jsRuntime.InvokeVoidAsync("moonlight.assets.loadStylesheet", styleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Plugins
|
#region Plugins
|
||||||
|
|
||||||
private async Task LoadPlugins()
|
|
||||||
{
|
|
||||||
// Create everything required to stream plugins
|
|
||||||
using var clientForStreaming = new HttpClient();
|
|
||||||
|
|
||||||
clientForStreaming.BaseAddress = new Uri(Configuration.HostEnvironment == "ApiServer"
|
|
||||||
? Configuration.ApiUrl
|
|
||||||
: WebAssemblyHostBuilder.HostEnvironment.BaseAddress
|
|
||||||
);
|
|
||||||
|
|
||||||
PluginLoadContext = new AssemblyLoadContext(null);
|
|
||||||
|
|
||||||
foreach (var assembly in Configuration.Assemblies)
|
|
||||||
{
|
|
||||||
var assemblyStream = await clientForStreaming.GetStreamAsync($"plugins/{assembly}");
|
|
||||||
PluginLoadContext.LoadFromStream(assemblyStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add application assembly service
|
|
||||||
var appAssemblyService = new ApplicationAssemblyService();
|
|
||||||
|
|
||||||
appAssemblyService.Assemblies.AddRange(AdditionalAssemblies);
|
|
||||||
appAssemblyService.Assemblies.AddRange(PluginLoadContext.Assemblies);
|
|
||||||
|
|
||||||
WebAssemblyHostBuilder.Services.AddSingleton(appAssemblyService);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task InitializePlugins()
|
private Task InitializePlugins()
|
||||||
{
|
{
|
||||||
// Define minimal service collection
|
// Define minimal service collection
|
||||||
@@ -205,38 +181,31 @@ public class Startup
|
|||||||
builder.AddProviders(LoggerProviders);
|
builder.AddProviders(LoggerProviders);
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
PluginLoadServiceProvider = startupSc.BuildServiceProvider();
|
||||||
var startupSp = startupSc.BuildServiceProvider();
|
|
||||||
|
|
||||||
// Initialize plugin startups
|
|
||||||
var startups = new List<IPluginStartup>();
|
|
||||||
var startupType = typeof(IPluginStartup);
|
|
||||||
|
|
||||||
var assembliesToScan = new List<Assembly>();
|
|
||||||
|
|
||||||
assembliesToScan.Add(typeof(Startup).Assembly);
|
|
||||||
assembliesToScan.AddRange(AdditionalAssemblies);
|
|
||||||
assembliesToScan.AddRange(PluginLoadContext.Assemblies);
|
|
||||||
|
|
||||||
foreach (var pluginAssembly in assembliesToScan)
|
// Collect startups
|
||||||
{
|
var pluginStartups = new List<IPluginStartup>();
|
||||||
var startupTypes = pluginAssembly
|
|
||||||
.ExportedTypes
|
|
||||||
.Where(x => !x.IsAbstract && !x.IsInterface && x.IsAssignableTo(startupType))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var type in startupTypes)
|
pluginStartups.Add(new CoreStartup());
|
||||||
{
|
|
||||||
var startup = ActivatorUtilities.CreateInstance(startupSp, type) as IPluginStartup;
|
|
||||||
|
|
||||||
if(startup == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
startups.Add(startup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginStartups = startups.ToArray();
|
pluginStartups.AddRange(AdditionalPlugins); // Used by the development server
|
||||||
|
|
||||||
|
// Do NOT remove the following comment, as its used to place the plugin startup register calls
|
||||||
|
// MLBUILD_PLUGIN_STARTUP_HERE
|
||||||
|
|
||||||
|
|
||||||
|
PluginStartups = pluginStartups.ToArray();
|
||||||
|
|
||||||
|
// Add application assembly service
|
||||||
|
var appAssemblyService = new ApplicationAssemblyService();
|
||||||
|
|
||||||
|
appAssemblyService.Assemblies.AddRange(
|
||||||
|
PluginStartups
|
||||||
|
.Select(x => x.GetType().Assembly)
|
||||||
|
.Distinct()
|
||||||
|
);
|
||||||
|
|
||||||
|
WebAssemblyHostBuilder.Services.AddSingleton(appAssemblyService);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -249,7 +218,7 @@ public class Startup
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await pluginAppStartup.BuildApplication(WebAssemblyHostBuilder);
|
await pluginAppStartup.BuildApplication(PluginLoadServiceProvider, WebAssemblyHostBuilder);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -268,7 +237,7 @@ public class Startup
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await pluginAppStartup.ConfigureApplication(WebAssemblyHost);
|
await pluginAppStartup.ConfigureApplication(PluginLoadServiceProvider, WebAssemblyHost);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -336,9 +305,9 @@ public class Startup
|
|||||||
{
|
{
|
||||||
WebAssemblyHostBuilder.Services.AddAuthorizationCore();
|
WebAssemblyHostBuilder.Services.AddAuthorizationCore();
|
||||||
WebAssemblyHostBuilder.Services.AddCascadingAuthenticationState();
|
WebAssemblyHostBuilder.Services.AddCascadingAuthenticationState();
|
||||||
|
|
||||||
WebAssemblyHostBuilder.Services.AddAuthenticationStateManager<RemoteAuthStateManager>();
|
WebAssemblyHostBuilder.Services.AddAuthenticationStateManager<RemoteAuthStateManager>();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,15 @@ window.moonlight = {
|
|||||||
scriptElement.type = 'text/javascript';
|
scriptElement.type = 'text/javascript';
|
||||||
|
|
||||||
(document.head || document.documentElement).appendChild(scriptElement);
|
(document.head || document.documentElement).appendChild(scriptElement);
|
||||||
|
},
|
||||||
|
loadStylesheet: function (url) {
|
||||||
|
let linkElement = document.createElement('link');
|
||||||
|
|
||||||
|
linkElement.href = url;
|
||||||
|
linkElement.type = 'text/css';
|
||||||
|
linkElement.rel = 'stylesheet';
|
||||||
|
|
||||||
|
(document.head || document.documentElement).appendChild(linkElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
<PropertyGroup>
|
<Title>Moonlight.Shared</Title>
|
||||||
<PackageId>Moonlight.Shared</PackageId>
|
<PackageTags>shared</PackageTags>
|
||||||
<Version>2.1.0</Version>
|
<PackageId>Moonlight.Shared</PackageId>
|
||||||
<Authors>Moonlight Panel</Authors>
|
<Version>2.1.0</Version>
|
||||||
<Description>A build of the shared classes for moonlight development</Description>
|
<Authors>Moonlight Panel</Authors>
|
||||||
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
<Description>A build of the shared classes for moonlight development</Description>
|
||||||
<DevelopmentDependency>true</DevelopmentDependency>
|
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<DevelopmentDependency>true</DevelopmentDependency>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
</Project>
|
|
||||||
227
Resources/Scripts/Commands/PackCommand.cs
Normal file
227
Resources/Scripts/Commands/PackCommand.cs
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Cocona;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Scripts.Helpers;
|
||||||
|
|
||||||
|
namespace Scripts.Commands;
|
||||||
|
|
||||||
|
public class PackCommand
|
||||||
|
{
|
||||||
|
private readonly string TmpDir = "/tmp/mlbuild";
|
||||||
|
private readonly ILogger<PackCommand> Logger;
|
||||||
|
private readonly CsprojHelper CsprojHelper;
|
||||||
|
private readonly NupkgHelper NupkgHelper;
|
||||||
|
|
||||||
|
private readonly string[] ValidTags = ["apiserver", "frontend", "shared"];
|
||||||
|
|
||||||
|
public PackCommand(
|
||||||
|
ILogger<PackCommand> logger,
|
||||||
|
CsprojHelper csprojHelper,
|
||||||
|
NupkgHelper nupkgHelper
|
||||||
|
)
|
||||||
|
{
|
||||||
|
CsprojHelper = csprojHelper;
|
||||||
|
NupkgHelper = nupkgHelper;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("pack", Description = "Packs the specified folder/solution into nuget packages")]
|
||||||
|
public async Task Pack(
|
||||||
|
[Argument] string solutionDirectory,
|
||||||
|
[Argument] string outputLocation,
|
||||||
|
[Option] string buildConfiguration = "Debug"
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(solutionDirectory))
|
||||||
|
{
|
||||||
|
Logger.LogError("The specified solution directory does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(outputLocation))
|
||||||
|
Directory.CreateDirectory(outputLocation);
|
||||||
|
|
||||||
|
if (Directory.Exists(TmpDir))
|
||||||
|
Directory.Delete(TmpDir, true);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(TmpDir);
|
||||||
|
|
||||||
|
// Find the project files
|
||||||
|
Logger.LogInformation("Searching for projects inside the specified folder");
|
||||||
|
|
||||||
|
var projects = await CsprojHelper.FindProjectsInPath(solutionDirectory, ValidTags);
|
||||||
|
|
||||||
|
// Show the user
|
||||||
|
Logger.LogInformation("Found {count} project(s) to check:", projects.Count);
|
||||||
|
|
||||||
|
foreach (var path in projects.Keys)
|
||||||
|
Logger.LogInformation("- {path}", Path.GetFullPath(path));
|
||||||
|
|
||||||
|
// Filter out project files which have specific tags specified
|
||||||
|
Logger.LogInformation("Filtering projects by tags");
|
||||||
|
|
||||||
|
var apiServerProjects = projects
|
||||||
|
.Where(x => x.Value.PackageTags.Contains("apiserver", StringComparer.InvariantCultureIgnoreCase))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var frontendProjects = projects
|
||||||
|
.Where(x => x.Value.PackageTags.Contains("frontend", StringComparer.InvariantCultureIgnoreCase))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var sharedProjects = projects
|
||||||
|
.Where(x => x.Value.PackageTags.Contains("shared", StringComparer.InvariantCultureIgnoreCase))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
Logger.LogInformation(
|
||||||
|
"Found {apiServerCount} api server project(s), {frontendCount} frontend project(s) and {sharedCount} shared project(s)",
|
||||||
|
apiServerProjects.Length,
|
||||||
|
frontendProjects.Length,
|
||||||
|
sharedProjects.Length
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now build all these projects so we can pack them
|
||||||
|
Logger.LogInformation("Building and packing api server project(s)");
|
||||||
|
|
||||||
|
foreach (var apiServerProject in apiServerProjects)
|
||||||
|
{
|
||||||
|
var csProjectFile = apiServerProject.Key;
|
||||||
|
var manifest = apiServerProject.Value;
|
||||||
|
|
||||||
|
await CsprojHelper.Build(
|
||||||
|
csProjectFile,
|
||||||
|
buildConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
var nugetFilePath = await CsprojHelper.Pack(
|
||||||
|
csProjectFile,
|
||||||
|
TmpDir,
|
||||||
|
buildConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
var nugetPackage = ZipFile.Open(
|
||||||
|
nugetFilePath,
|
||||||
|
ZipArchiveMode.Update
|
||||||
|
);
|
||||||
|
|
||||||
|
await NupkgHelper.RemoveContentFiles(nugetPackage);
|
||||||
|
|
||||||
|
// We don't want to clean moonlight references when we are packing moonlight,
|
||||||
|
// as it would remove references to its own shared project
|
||||||
|
|
||||||
|
if (!manifest.PackageId.StartsWith("Moonlight."))
|
||||||
|
await NupkgHelper.CleanDependencies(nugetPackage, "Moonlight.");
|
||||||
|
|
||||||
|
Logger.LogInformation("Finishing package and copying to output directory");
|
||||||
|
|
||||||
|
nugetPackage.Dispose();
|
||||||
|
|
||||||
|
File.Move(
|
||||||
|
nugetFilePath,
|
||||||
|
Path.Combine(outputLocation, Path.GetFileName(nugetFilePath)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Building and packing frontend projects");
|
||||||
|
|
||||||
|
foreach (var frontendProject in frontendProjects)
|
||||||
|
{
|
||||||
|
var csProjectFile = frontendProject.Key;
|
||||||
|
var manifest = frontendProject.Value;
|
||||||
|
|
||||||
|
await CsprojHelper.Build(
|
||||||
|
csProjectFile,
|
||||||
|
buildConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
var nugetFilePath = await CsprojHelper.Pack(
|
||||||
|
csProjectFile,
|
||||||
|
TmpDir,
|
||||||
|
buildConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
var nugetPackage = ZipFile.Open(
|
||||||
|
nugetFilePath,
|
||||||
|
ZipArchiveMode.Update
|
||||||
|
);
|
||||||
|
|
||||||
|
await NupkgHelper.RemoveStaticWebAssets(nugetPackage, "_framework");
|
||||||
|
await NupkgHelper.RemoveStaticWebAssets(nugetPackage, "css/style.min.css");
|
||||||
|
await NupkgHelper.RemoveContentFiles(nugetPackage);
|
||||||
|
|
||||||
|
// We don't want to clean moonlight references when we are packing moonlight,
|
||||||
|
// as it would remove references to its own shared project
|
||||||
|
|
||||||
|
if (!manifest.PackageId.StartsWith("Moonlight."))
|
||||||
|
await NupkgHelper.CleanDependencies(nugetPackage, "Moonlight.");
|
||||||
|
|
||||||
|
|
||||||
|
// Pack razor and html files into src folder
|
||||||
|
var additionalSrcFiles = new List<string>();
|
||||||
|
var basePath = Path.GetDirectoryName(csProjectFile)!;
|
||||||
|
|
||||||
|
additionalSrcFiles.AddRange(
|
||||||
|
Directory.GetFiles(basePath, "*.razor", SearchOption.AllDirectories)
|
||||||
|
);
|
||||||
|
|
||||||
|
additionalSrcFiles.AddRange(
|
||||||
|
Directory.GetFiles(basePath, "index.html", SearchOption.AllDirectories)
|
||||||
|
);
|
||||||
|
|
||||||
|
await NupkgHelper.AddSourceFiles(
|
||||||
|
nugetPackage,
|
||||||
|
additionalSrcFiles.ToArray(),
|
||||||
|
file => "src/" + file.Replace(basePath, "").Trim('/')
|
||||||
|
);
|
||||||
|
|
||||||
|
Logger.LogInformation("Finishing package and copying to output directory");
|
||||||
|
|
||||||
|
nugetPackage.Dispose();
|
||||||
|
|
||||||
|
File.Move(
|
||||||
|
nugetFilePath,
|
||||||
|
Path.Combine(outputLocation, Path.GetFileName(nugetFilePath)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Building and packing shared projects");
|
||||||
|
|
||||||
|
foreach (var sharedProject in sharedProjects)
|
||||||
|
{
|
||||||
|
var csProjectFile = sharedProject.Key;
|
||||||
|
var manifest = sharedProject.Value;
|
||||||
|
|
||||||
|
await CsprojHelper.Build(
|
||||||
|
csProjectFile,
|
||||||
|
buildConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
var nugetFilePath = await CsprojHelper.Pack(
|
||||||
|
csProjectFile,
|
||||||
|
TmpDir,
|
||||||
|
buildConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
var nugetPackage = ZipFile.Open(
|
||||||
|
nugetFilePath,
|
||||||
|
ZipArchiveMode.Update
|
||||||
|
);
|
||||||
|
|
||||||
|
// We don't want to clean moonlight references when we are packing moonlight,
|
||||||
|
// as it would remove references to its own shared project
|
||||||
|
|
||||||
|
if (!manifest.PackageId.StartsWith("Moonlight."))
|
||||||
|
await NupkgHelper.CleanDependencies(nugetPackage, "Moonlight.");
|
||||||
|
|
||||||
|
nugetPackage.Dispose();
|
||||||
|
|
||||||
|
File.Move(
|
||||||
|
nugetFilePath,
|
||||||
|
Path.Combine(outputLocation, Path.GetFileName(nugetFilePath)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
265
Resources/Scripts/Commands/PreBuildCommand.cs
Normal file
265
Resources/Scripts/Commands/PreBuildCommand.cs
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Cocona;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Scripts.Helpers;
|
||||||
|
using Scripts.Models;
|
||||||
|
|
||||||
|
namespace Scripts.Commands;
|
||||||
|
|
||||||
|
public class PreBuildCommand
|
||||||
|
{
|
||||||
|
private readonly NupkgHelper NupkgHelper;
|
||||||
|
private readonly CsprojHelper CsprojHelper;
|
||||||
|
private readonly CodeHelper CodeHelper;
|
||||||
|
private readonly ILogger<PreBuildCommand> Logger;
|
||||||
|
|
||||||
|
private const string GeneratedStart = "// MLBUILD Generated Start";
|
||||||
|
private const string GeneratedEnd = "// MLBUILD Generated End";
|
||||||
|
private const string GeneratedHook = "// MLBUILD_PLUGIN_STARTUP_HERE";
|
||||||
|
|
||||||
|
private readonly string[] ValidTags = ["frontend", "apiserver", "shared"];
|
||||||
|
|
||||||
|
public PreBuildCommand(
|
||||||
|
CsprojHelper csprojHelper,
|
||||||
|
NupkgHelper nupkgHelper,
|
||||||
|
CodeHelper codeHelper,
|
||||||
|
ILogger<PreBuildCommand> logger
|
||||||
|
)
|
||||||
|
{
|
||||||
|
CsprojHelper = csprojHelper;
|
||||||
|
NupkgHelper = nupkgHelper;
|
||||||
|
CodeHelper = codeHelper;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("prebuild")]
|
||||||
|
public async Task Prebuild(
|
||||||
|
[Argument] string moonlightDirectory,
|
||||||
|
[Argument] string pluginsDirectory
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var projects = await CsprojHelper.FindProjectsInPath(moonlightDirectory, ValidTags);
|
||||||
|
|
||||||
|
var nugetManifests = await GetNugetManifests(pluginsDirectory);
|
||||||
|
|
||||||
|
Logger.LogInformation("Following plugins found:");
|
||||||
|
|
||||||
|
foreach (var manifest in nugetManifests)
|
||||||
|
{
|
||||||
|
Logger.LogInformation(
|
||||||
|
"- {id} ({version}) [{tags}]",
|
||||||
|
manifest.Id,
|
||||||
|
manifest.Version,
|
||||||
|
string.Join(", ", manifest.Tags)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Adjusting csproj files");
|
||||||
|
|
||||||
|
foreach (var project in projects)
|
||||||
|
{
|
||||||
|
var csProjectPath = project.Key;
|
||||||
|
|
||||||
|
await using var fs = File.Open(
|
||||||
|
csProjectPath,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.ReadWrite,
|
||||||
|
FileShare.ReadWrite
|
||||||
|
);
|
||||||
|
|
||||||
|
var document = await XDocument.LoadAsync(fs, LoadOptions.None, CancellationToken.None);
|
||||||
|
fs.Position = 0;
|
||||||
|
|
||||||
|
var dependenciesToAdd = nugetManifests
|
||||||
|
.Where(x => x.Tags.Any(tag =>
|
||||||
|
project.Value.PackageTags.Contains(tag, StringComparer.InvariantCultureIgnoreCase)))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
await CsprojHelper.CleanDependencies(document, "MoonlightBuildDeps");
|
||||||
|
await CsprojHelper.AddDependencies(document, dependenciesToAdd, "MoonlightBuildDeps");
|
||||||
|
|
||||||
|
fs.Position = 0;
|
||||||
|
await document.SaveAsync(fs, SaveOptions.None, CancellationToken.None);
|
||||||
|
|
||||||
|
await fs.FlushAsync();
|
||||||
|
fs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Restoring projects");
|
||||||
|
|
||||||
|
foreach (var csProjectPath in projects.Keys)
|
||||||
|
await CsprojHelper.Restore(csProjectPath);
|
||||||
|
|
||||||
|
Logger.LogInformation("Generating plugin startup");
|
||||||
|
|
||||||
|
foreach (var currentTag in ValidTags)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Checking for '{currentTag}' projects", currentTag);
|
||||||
|
|
||||||
|
var projectsWithTag = projects
|
||||||
|
.Where(x =>
|
||||||
|
x.Value.PackageTags.Contains(currentTag, StringComparer.InvariantCultureIgnoreCase)
|
||||||
|
)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var project in projectsWithTag)
|
||||||
|
{
|
||||||
|
var csProjectPath = project.Key;
|
||||||
|
|
||||||
|
var currentDependencies = nugetManifests
|
||||||
|
.Where(x => x.Tags.Contains(currentTag))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var classPaths = await FindStartupClasses(currentDependencies);
|
||||||
|
|
||||||
|
var code = new StringBuilder();
|
||||||
|
|
||||||
|
code.AppendLine(GeneratedStart);
|
||||||
|
|
||||||
|
foreach (var path in classPaths)
|
||||||
|
code.AppendLine($"pluginStartups.Add(new global::{path}());");
|
||||||
|
|
||||||
|
code.Append(GeneratedEnd);
|
||||||
|
|
||||||
|
var filesToSearch = Directory.GetFiles(
|
||||||
|
Path.GetDirectoryName(csProjectPath)!,
|
||||||
|
"*.cs",
|
||||||
|
SearchOption.AllDirectories
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var file in filesToSearch)
|
||||||
|
{
|
||||||
|
var content = await File.ReadAllTextAsync(file);
|
||||||
|
|
||||||
|
if (!content.Contains(GeneratedHook, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Logger.LogInformation("Injecting generated code to: {path}", Path.GetFullPath(file));
|
||||||
|
|
||||||
|
content = content.Replace(
|
||||||
|
GeneratedHook,
|
||||||
|
code.ToString(),
|
||||||
|
StringComparison.InvariantCultureIgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(file, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("An error occured while prebuilding moonlight. Removing csproj modifications");
|
||||||
|
|
||||||
|
foreach (var project in projects)
|
||||||
|
{
|
||||||
|
await CsprojHelper.CleanDependencies(project.Key, "MoonlightBuildDeps");
|
||||||
|
|
||||||
|
var path = Path.GetDirectoryName(project.Key)!;
|
||||||
|
await RemoveGeneratedCode(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("prebuild-reset")]
|
||||||
|
public async Task PrebuildReset(
|
||||||
|
[Argument] string moonlightDir
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var projects = await CsprojHelper.FindProjectsInPath(moonlightDir, ValidTags);
|
||||||
|
|
||||||
|
Logger.LogInformation("Reverting csproj changes");
|
||||||
|
|
||||||
|
foreach (var project in projects)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Removing dependencies: {project}", project.Key);
|
||||||
|
await CsprojHelper.CleanDependencies(project.Key, "MoonlightBuildDeps");
|
||||||
|
|
||||||
|
Logger.LogInformation("Removing generated code: {project}", project.Key);
|
||||||
|
var path = Path.GetDirectoryName(project.Key)!;
|
||||||
|
await RemoveGeneratedCode(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<NupkgManifest[]> GetNugetManifests(string nugetDir)
|
||||||
|
{
|
||||||
|
var nugetFiles = Directory.GetFiles(
|
||||||
|
nugetDir,
|
||||||
|
"*.nupkg",
|
||||||
|
SearchOption.AllDirectories
|
||||||
|
);
|
||||||
|
|
||||||
|
var manifests = new List<NupkgManifest>();
|
||||||
|
|
||||||
|
foreach (var nugetFilePath in nugetFiles)
|
||||||
|
{
|
||||||
|
using var nugetPackage = ZipFile.Open(nugetFilePath, ZipArchiveMode.Read);
|
||||||
|
var manifest = await NupkgHelper.GetManifest(nugetPackage);
|
||||||
|
|
||||||
|
if (manifest == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
manifests.Add(manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifests.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string[]> FindStartupClasses(NupkgManifest[] dependencies)
|
||||||
|
{
|
||||||
|
var nugetPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||||
|
".nuget",
|
||||||
|
"packages"
|
||||||
|
);
|
||||||
|
|
||||||
|
var filesToScan = dependencies
|
||||||
|
.SelectMany(dependency =>
|
||||||
|
{
|
||||||
|
var dependencySrcPath = Path.Combine(nugetPath, dependency.Id.ToLower(), dependency.Version, "src");
|
||||||
|
|
||||||
|
Logger.LogDebug("Checking {dependencySrcPath}", dependencySrcPath);
|
||||||
|
|
||||||
|
if (!Directory.Exists(dependencySrcPath))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
return Directory.GetFiles(dependencySrcPath, "*.cs", SearchOption.AllDirectories);
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return await CodeHelper.FindPluginStartups(filesToScan);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveGeneratedCode(string dir)
|
||||||
|
{
|
||||||
|
var filesToSearch = Directory.GetFiles(
|
||||||
|
dir,
|
||||||
|
"*.cs",
|
||||||
|
SearchOption.AllDirectories
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var file in filesToSearch)
|
||||||
|
{
|
||||||
|
var content = await File.ReadAllTextAsync(file);
|
||||||
|
|
||||||
|
if (!content.Contains(GeneratedStart) || !content.Contains(GeneratedEnd))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var startIndex = content.IndexOf(GeneratedStart, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
var endIndex = content.IndexOf(GeneratedEnd, startIndex, StringComparison.InvariantCultureIgnoreCase) +
|
||||||
|
GeneratedEnd.Length;
|
||||||
|
|
||||||
|
var cutOut = content.Substring(startIndex, endIndex - startIndex);
|
||||||
|
|
||||||
|
content = content.Replace(cutOut, GeneratedHook);
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(file, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using System.IO.Compression;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace Scripts.Functions;
|
|
||||||
|
|
||||||
public static class ContentFunctions
|
|
||||||
{
|
|
||||||
public static async Task Run(string[] args)
|
|
||||||
{
|
|
||||||
if (args.Length < 2)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Please provide the path to a nuget file and at least one regex expression");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nugetPath = args[0];
|
|
||||||
|
|
||||||
var regexs = args
|
|
||||||
.Skip(1)
|
|
||||||
.Select(x => new Regex(x))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
Console.WriteLine(string.Join(", ", args
|
|
||||||
.Skip(1)
|
|
||||||
.Select(x => new Regex(x))));
|
|
||||||
|
|
||||||
if (!File.Exists(nugetPath))
|
|
||||||
{
|
|
||||||
Console.WriteLine("The provided file does not exist");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("Modding nuget package...");
|
|
||||||
using var zipFile = ZipFile.Open(nugetPath, ZipArchiveMode.Update);
|
|
||||||
|
|
||||||
foreach (var zipArchiveEntry in zipFile.Entries)
|
|
||||||
{
|
|
||||||
Console.WriteLine(zipArchiveEntry.FullName);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("Searching for files to remove");
|
|
||||||
var files = zipFile.Entries
|
|
||||||
.Where(x => x.FullName.Trim('/').StartsWith("content"))
|
|
||||||
.Where(x =>
|
|
||||||
{
|
|
||||||
var name = x.FullName
|
|
||||||
.Replace("contentFiles/", "")
|
|
||||||
.Replace("content/", "");
|
|
||||||
|
|
||||||
Console.WriteLine(name);
|
|
||||||
|
|
||||||
return regexs.Any(y => y.IsMatch(name));
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
Console.WriteLine($"Found {files.Length} file(s) to remove");
|
|
||||||
foreach (var file in files)
|
|
||||||
file.Delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
using System.IO.Compression;
|
|
||||||
|
|
||||||
namespace Scripts.Functions;
|
|
||||||
|
|
||||||
public static class SrcFunctions
|
|
||||||
{
|
|
||||||
public static async Task Run(string[] args)
|
|
||||||
{
|
|
||||||
if (args.Length != 3)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Please provide the path to a nuget file, a search pattern and a path");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nugetPath = args[0];
|
|
||||||
var path = args[1];
|
|
||||||
var pattern = args[2];
|
|
||||||
|
|
||||||
if (!File.Exists(nugetPath))
|
|
||||||
{
|
|
||||||
Console.WriteLine("The provided file does not exist");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("Modding nuget package...");
|
|
||||||
using var zipFile = ZipFile.Open(nugetPath, ZipArchiveMode.Update);
|
|
||||||
|
|
||||||
var filesToAdd = Directory.GetFiles(path, pattern, SearchOption.AllDirectories);
|
|
||||||
|
|
||||||
foreach (var file in filesToAdd)
|
|
||||||
{
|
|
||||||
var name = file.Replace(path, "").Replace("\\", "/");
|
|
||||||
|
|
||||||
Console.WriteLine($"{file} => /src/{name}");
|
|
||||||
|
|
||||||
var entry = zipFile.CreateEntry($"src/{name}");
|
|
||||||
await using var entryStream = entry.Open();
|
|
||||||
|
|
||||||
await using var fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
||||||
await fs.CopyToAsync(entryStream);
|
|
||||||
fs.Close();
|
|
||||||
|
|
||||||
await entryStream.FlushAsync();
|
|
||||||
entryStream.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
using System.IO.Compression;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace Scripts.Functions;
|
|
||||||
|
|
||||||
public static class StaticWebAssetsFunctions
|
|
||||||
{
|
|
||||||
public static async Task Run(string[] args)
|
|
||||||
{
|
|
||||||
if (args.Length < 2)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Please provide the path to a nuget file and at least one regex expression");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nugetPath = args[0];
|
|
||||||
|
|
||||||
var regexs = args
|
|
||||||
.Skip(1)
|
|
||||||
.Select(x => new Regex(x))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (!File.Exists(nugetPath))
|
|
||||||
{
|
|
||||||
Console.WriteLine("The provided file does not exist");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("Modding nuget package...");
|
|
||||||
using var zipFile = ZipFile.Open(nugetPath, ZipArchiveMode.Update);
|
|
||||||
|
|
||||||
Console.WriteLine("Searching for files to remove");
|
|
||||||
var files = zipFile.Entries
|
|
||||||
.Where(x => x.FullName.Trim('/').StartsWith("staticwebassets"))
|
|
||||||
.Where(x =>
|
|
||||||
{
|
|
||||||
var name = x.FullName.Replace("staticwebassets/", "");
|
|
||||||
|
|
||||||
return regexs.Any(y => y.IsMatch(name));
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
Console.WriteLine($"Found {files.Length} file(s) to remove");
|
|
||||||
foreach (var file in files)
|
|
||||||
file.Delete();
|
|
||||||
|
|
||||||
Console.WriteLine("Modifying static web assets build target");
|
|
||||||
var oldBuildTargetEntry = zipFile
|
|
||||||
.Entries
|
|
||||||
.FirstOrDefault(x => x.FullName == "build/Microsoft.AspNetCore.StaticWebAssets.props");
|
|
||||||
|
|
||||||
if (oldBuildTargetEntry == null)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Build target file not found in nuget packages");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var oldBuildTargetStream = oldBuildTargetEntry.Open();
|
|
||||||
|
|
||||||
var contentXml = await XDocument.LoadAsync(
|
|
||||||
oldBuildTargetStream,
|
|
||||||
LoadOptions.None,
|
|
||||||
CancellationToken.None
|
|
||||||
);
|
|
||||||
|
|
||||||
oldBuildTargetStream.Close();
|
|
||||||
oldBuildTargetEntry.Delete();
|
|
||||||
|
|
||||||
var assetRefsToRemove = contentXml
|
|
||||||
.Descendants("StaticWebAsset")
|
|
||||||
.Where(asset =>
|
|
||||||
{
|
|
||||||
var element = asset.Element("RelativePath");
|
|
||||||
|
|
||||||
if (element == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return regexs.Any(y => y.IsMatch(element.Value));
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var asset in assetRefsToRemove)
|
|
||||||
asset.Remove();
|
|
||||||
|
|
||||||
var newBuildTargetEntry = zipFile.CreateEntry("build/Microsoft.AspNetCore.StaticWebAssets.props");
|
|
||||||
await using var newBuildTargetStream = newBuildTargetEntry.Open();
|
|
||||||
|
|
||||||
await contentXml.SaveAsync(newBuildTargetStream, SaveOptions.None, CancellationToken.None);
|
|
||||||
|
|
||||||
newBuildTargetStream.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
73
Resources/Scripts/Helpers/CodeHelper.cs
Normal file
73
Resources/Scripts/Helpers/CodeHelper.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Scripts.Helpers;
|
||||||
|
|
||||||
|
public class CodeHelper
|
||||||
|
{
|
||||||
|
private readonly ILogger<CodeHelper> Logger;
|
||||||
|
|
||||||
|
public CodeHelper(ILogger<CodeHelper> logger)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string[]> FindPluginStartups(string[] filesToSearch)
|
||||||
|
{
|
||||||
|
var result = new List<string>();
|
||||||
|
|
||||||
|
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
|
||||||
|
|
||||||
|
var trees = new List<SyntaxTree>();
|
||||||
|
|
||||||
|
foreach (var file in filesToSearch)
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Reading {file}", file);
|
||||||
|
|
||||||
|
var content = await File.ReadAllTextAsync(file);
|
||||||
|
var tree = CSharpSyntaxTree.ParseText(content);
|
||||||
|
trees.Add(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
var compilation = CSharpCompilation.Create("Analysis", trees, [mscorlib]);
|
||||||
|
|
||||||
|
foreach (var tree in trees)
|
||||||
|
{
|
||||||
|
var model = compilation.GetSemanticModel(tree);
|
||||||
|
var root = await tree.GetRootAsync();
|
||||||
|
|
||||||
|
var classDeclarations = root
|
||||||
|
.DescendantNodes()
|
||||||
|
.OfType<ClassDeclarationSyntax>();
|
||||||
|
|
||||||
|
foreach (var classDeclaration in classDeclarations)
|
||||||
|
{
|
||||||
|
var symbol = model.GetDeclaredSymbol(classDeclaration);
|
||||||
|
|
||||||
|
if (symbol == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var hasAttribute = symbol.GetAttributes().Any(attr =>
|
||||||
|
{
|
||||||
|
if (attr.AttributeClass == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return attr.AttributeClass.Name == "PluginStartup";
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasAttribute)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var classPath = $"{symbol.ContainingNamespace.ToDisplayString()}.{classDeclaration.Identifier.ValueText}";
|
||||||
|
|
||||||
|
Logger.LogInformation("Detected startup in class: {classPath}", classPath);
|
||||||
|
|
||||||
|
result.Add(classPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Resources/Scripts/Helpers/CommandHelper.cs
Normal file
30
Resources/Scripts/Helpers/CommandHelper.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Scripts.Helpers;
|
||||||
|
|
||||||
|
public class CommandHelper
|
||||||
|
{
|
||||||
|
public async Task Run(string program, string arguments, string? workingDir = null)
|
||||||
|
{
|
||||||
|
var process = await RunRaw(program, arguments, workingDir);
|
||||||
|
|
||||||
|
await process.WaitForExitAsync();
|
||||||
|
|
||||||
|
if (process.ExitCode != 0)
|
||||||
|
throw new Exception($"The command '{program} {arguments}' failed with exit code: {process.ExitCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<Process> RunRaw(string program, string arguments, string? workingDir = null)
|
||||||
|
{
|
||||||
|
var psi = new ProcessStartInfo()
|
||||||
|
{
|
||||||
|
FileName = program,
|
||||||
|
Arguments = arguments,
|
||||||
|
WorkingDirectory = string.IsNullOrEmpty(workingDir) ? Directory.GetCurrentDirectory() : workingDir
|
||||||
|
};
|
||||||
|
|
||||||
|
var process = Process.Start(psi)!;
|
||||||
|
|
||||||
|
return Task.FromResult(process);
|
||||||
|
}
|
||||||
|
}
|
||||||
228
Resources/Scripts/Helpers/CsprojHelper.cs
Normal file
228
Resources/Scripts/Helpers/CsprojHelper.cs
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
using System.Collections.Frozen;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Scripts.Models;
|
||||||
|
|
||||||
|
namespace Scripts.Helpers;
|
||||||
|
|
||||||
|
public class CsprojHelper
|
||||||
|
{
|
||||||
|
private readonly ILogger<CsprojHelper> Logger;
|
||||||
|
private readonly CommandHelper CommandHelper;
|
||||||
|
|
||||||
|
public CsprojHelper(ILogger<CsprojHelper> logger, CommandHelper commandHelper)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
CommandHelper = commandHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Add dependencies
|
||||||
|
|
||||||
|
public async Task AddDependencies(string path, NupkgManifest[] dependencies, string label)
|
||||||
|
{
|
||||||
|
await using var fs = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||||
|
fs.Position = 0;
|
||||||
|
|
||||||
|
await AddDependencies(fs, dependencies, label);
|
||||||
|
|
||||||
|
await fs.FlushAsync();
|
||||||
|
fs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddDependencies(Stream stream, NupkgManifest[] dependencies, string label)
|
||||||
|
{
|
||||||
|
var xmlDocument = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None);
|
||||||
|
|
||||||
|
await AddDependencies(xmlDocument, dependencies, label);
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
await xmlDocument.SaveAsync(stream, SaveOptions.DisableFormatting, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task AddDependencies(XDocument document, NupkgManifest[] dependencies, string label)
|
||||||
|
{
|
||||||
|
var project = document.Element("Project")!;
|
||||||
|
|
||||||
|
var itemGroup = new XElement("ItemGroup");
|
||||||
|
itemGroup.SetAttributeValue("Label", label);
|
||||||
|
|
||||||
|
foreach (var dependency in dependencies)
|
||||||
|
{
|
||||||
|
var depElement = new XElement("PackageReference");
|
||||||
|
depElement.SetAttributeValue("Include", dependency.Id);
|
||||||
|
depElement.SetAttributeValue("Version", dependency.Version);
|
||||||
|
|
||||||
|
itemGroup.Add(depElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
project.Add(itemGroup);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Clean dependencies
|
||||||
|
|
||||||
|
public async Task CleanDependencies(string path, string label)
|
||||||
|
{
|
||||||
|
var document = XDocument.Load(path, LoadOptions.None);
|
||||||
|
|
||||||
|
await CleanDependencies(document, label);
|
||||||
|
|
||||||
|
document.Save(path, SaveOptions.DisableFormatting);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CleanDependencies(Stream stream, string label)
|
||||||
|
{
|
||||||
|
var xmlDocument = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None);
|
||||||
|
|
||||||
|
await CleanDependencies(xmlDocument, label);
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
await xmlDocument.SaveAsync(stream, SaveOptions.DisableFormatting, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CleanDependencies(XDocument document, string label)
|
||||||
|
{
|
||||||
|
var itemGroupsToRemove = document
|
||||||
|
.Descendants("ItemGroup")
|
||||||
|
.Where(x => x.Attribute("Label")?.Value.Contains(label) ?? false)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
itemGroupsToRemove.Remove();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Read
|
||||||
|
|
||||||
|
public async Task<CsprojManifest> Read(string path)
|
||||||
|
{
|
||||||
|
await using var fileStream = File.Open(
|
||||||
|
path,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.ReadWrite
|
||||||
|
);
|
||||||
|
|
||||||
|
var manifest = await Read(fileStream);
|
||||||
|
|
||||||
|
fileStream.Close();
|
||||||
|
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CsprojManifest> Read(Stream stream)
|
||||||
|
{
|
||||||
|
var xmlDocument = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None);
|
||||||
|
return await Read(xmlDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<CsprojManifest> Read(XDocument document)
|
||||||
|
{
|
||||||
|
var manifest = new CsprojManifest();
|
||||||
|
|
||||||
|
var ns = document.Root!.GetDefaultNamespace();
|
||||||
|
|
||||||
|
manifest.IsPackable = document
|
||||||
|
.Descendants(ns + "IsPackable")
|
||||||
|
.FirstOrDefault()?.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase) ?? false;
|
||||||
|
|
||||||
|
manifest.PackageId = document
|
||||||
|
.Descendants(ns + "PackageId")
|
||||||
|
.FirstOrDefault()?.Value ?? "N/A";
|
||||||
|
|
||||||
|
manifest.Version = document
|
||||||
|
.Descendants(ns + "Version")
|
||||||
|
.FirstOrDefault()?.Value ?? "N/A";
|
||||||
|
|
||||||
|
manifest.PackageTags = document
|
||||||
|
.Descendants(ns + "PackageTags")
|
||||||
|
.FirstOrDefault()?.Value
|
||||||
|
.Split(";", StringSplitOptions.RemoveEmptyEntries) ?? [];
|
||||||
|
|
||||||
|
return Task.FromResult(manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public async Task Restore(string path)
|
||||||
|
{
|
||||||
|
var basePath = Path.GetFullPath(Path.GetDirectoryName(path)!);
|
||||||
|
var fileName = Path.GetFileName(path);
|
||||||
|
|
||||||
|
Logger.LogInformation("Restore: {basePath} - {fileName}", basePath, fileName);
|
||||||
|
|
||||||
|
await CommandHelper.Run(
|
||||||
|
"/usr/bin/dotnet",
|
||||||
|
$"restore {fileName}",
|
||||||
|
basePath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Build(string file, string configuration)
|
||||||
|
{
|
||||||
|
var basePath = Path.GetFullPath(Path.GetDirectoryName(file)!);
|
||||||
|
var fileName = Path.GetFileName(file);
|
||||||
|
|
||||||
|
await CommandHelper.Run(
|
||||||
|
"/usr/bin/dotnet",
|
||||||
|
$"build {fileName} --configuration {configuration}",
|
||||||
|
basePath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Pack(string file, string output, string configuration)
|
||||||
|
{
|
||||||
|
var basePath = Path.GetFullPath(Path.GetDirectoryName(file)!);
|
||||||
|
var fileName = Path.GetFileName(file);
|
||||||
|
|
||||||
|
await CommandHelper.Run(
|
||||||
|
"/usr/bin/dotnet",
|
||||||
|
$"pack {fileName} --output {output} --configuration {configuration}",
|
||||||
|
basePath
|
||||||
|
);
|
||||||
|
|
||||||
|
var nugetFilesPaths = Directory.GetFiles(
|
||||||
|
output,
|
||||||
|
"*.nupkg",
|
||||||
|
SearchOption.TopDirectoryOnly
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nugetFilesPaths.Length == 0)
|
||||||
|
throw new Exception("No nuget packages were built");
|
||||||
|
|
||||||
|
if (nugetFilesPaths.Length > 1)
|
||||||
|
throw new Exception("More than one nuget package has been built");
|
||||||
|
|
||||||
|
return nugetFilesPaths.First();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrozenDictionary<string, CsprojManifest>> FindProjectsInPath(string path, string[] validTags)
|
||||||
|
{
|
||||||
|
var projectFiles = Directory.GetFiles(
|
||||||
|
path,
|
||||||
|
"*.csproj",
|
||||||
|
SearchOption.AllDirectories
|
||||||
|
);
|
||||||
|
|
||||||
|
var projects = new Dictionary<string, CsprojManifest>();
|
||||||
|
|
||||||
|
foreach (var projectFile in projectFiles)
|
||||||
|
{
|
||||||
|
var manifest = await Read(projectFile);
|
||||||
|
|
||||||
|
// Ignore all projects which have no matching tags
|
||||||
|
if (!manifest.PackageTags.Any(projectTag =>
|
||||||
|
validTags.Contains(projectTag, StringComparer.InvariantCultureIgnoreCase)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
projects.Add(projectFile, manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects.ToFrozenDictionary();
|
||||||
|
}
|
||||||
|
}
|
||||||
195
Resources/Scripts/Helpers/NupkgHelper.cs
Normal file
195
Resources/Scripts/Helpers/NupkgHelper.cs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Scripts.Models;
|
||||||
|
|
||||||
|
namespace Scripts.Helpers;
|
||||||
|
|
||||||
|
public class NupkgHelper
|
||||||
|
{
|
||||||
|
private readonly ILogger<NupkgHelper> Logger;
|
||||||
|
|
||||||
|
public NupkgHelper(ILogger<NupkgHelper> logger)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NupkgManifest?> GetManifest(ZipArchive nugetPackage)
|
||||||
|
{
|
||||||
|
var nuspecEntry = nugetPackage.Entries.FirstOrDefault(
|
||||||
|
x => x.Name.EndsWith(".nuspec")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nuspecEntry == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
await using var fs = nuspecEntry.Open();
|
||||||
|
|
||||||
|
var nuspec = await XDocument.LoadAsync(fs, LoadOptions.None, CancellationToken.None);
|
||||||
|
|
||||||
|
var ns = nuspec.Root!.GetDefaultNamespace();
|
||||||
|
var metadata = nuspec.Root!.Element(ns + "metadata")!;
|
||||||
|
|
||||||
|
var id = metadata.Element(ns + "id")!.Value;
|
||||||
|
var version = metadata.Element(ns + "version")!.Value;
|
||||||
|
var tags = metadata.Element(ns + "tags")!.Value;
|
||||||
|
|
||||||
|
return new NupkgManifest()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Version = version,
|
||||||
|
Tags = tags.Split(";", StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CleanDependencies(ZipArchive nugetPackage, string filter)
|
||||||
|
{
|
||||||
|
var nuspecEntry = nugetPackage.Entries.FirstOrDefault(
|
||||||
|
x => x.Name.EndsWith(".nuspec")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nuspecEntry == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("No nuspec file to modify found in nuget package");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ModifyXmlInPackage(nugetPackage, nuspecEntry, document =>
|
||||||
|
{
|
||||||
|
var ns = document.Root!.GetDefaultNamespace();
|
||||||
|
|
||||||
|
return document
|
||||||
|
.Descendants(ns + "dependency")
|
||||||
|
.Where(x => x.Attribute("id")?.Value.StartsWith(filter) ?? false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveContentFiles(ZipArchive nugetPackage)
|
||||||
|
{
|
||||||
|
foreach (var entry in nugetPackage.Entries.ToArray())
|
||||||
|
{
|
||||||
|
if (!entry.FullName.StartsWith("contentFiles") && !entry.FullName.StartsWith("content"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Logger.LogDebug("Removing content file: {path}", entry.FullName);
|
||||||
|
entry.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
var nuspecFile = nugetPackage
|
||||||
|
.Entries
|
||||||
|
.FirstOrDefault(x => x.Name.EndsWith(".nuspec"));
|
||||||
|
|
||||||
|
if (nuspecFile == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Nuspec file missing. Unable to remove content files references from nuspec file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ModifyXmlInPackage(
|
||||||
|
nugetPackage,
|
||||||
|
nuspecFile,
|
||||||
|
document =>
|
||||||
|
{
|
||||||
|
var ns = document.Root!.GetDefaultNamespace();
|
||||||
|
return document.Descendants(ns + "contentFiles");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ZipArchiveEntry> ModifyXmlInPackage(
|
||||||
|
ZipArchive nugetPackage,
|
||||||
|
ZipArchiveEntry entry,
|
||||||
|
Func<XDocument, IEnumerable<XElement>> filter
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var oldPath = entry.FullName;
|
||||||
|
await using var oldFs = entry.Open();
|
||||||
|
|
||||||
|
var document = await XDocument.LoadAsync(
|
||||||
|
oldFs,
|
||||||
|
LoadOptions.None,
|
||||||
|
CancellationToken.None
|
||||||
|
);
|
||||||
|
|
||||||
|
var itemsToRemove = filter.Invoke(document);
|
||||||
|
var items = itemsToRemove.ToArray();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
item.Remove();
|
||||||
|
|
||||||
|
oldFs.Close();
|
||||||
|
entry.Delete();
|
||||||
|
|
||||||
|
var newEntry = nugetPackage.CreateEntry(oldPath);
|
||||||
|
var newFs = newEntry.Open();
|
||||||
|
|
||||||
|
await document.SaveAsync(newFs, SaveOptions.None, CancellationToken.None);
|
||||||
|
|
||||||
|
await newFs.FlushAsync();
|
||||||
|
newFs.Close();
|
||||||
|
|
||||||
|
return newEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveStaticWebAssets(ZipArchive nugetPackage, string filter)
|
||||||
|
{
|
||||||
|
var filterWithPath = $"staticwebassets/{filter}";
|
||||||
|
|
||||||
|
foreach (var entry in nugetPackage.Entries.ToArray())
|
||||||
|
{
|
||||||
|
if (!entry.FullName.StartsWith(filterWithPath))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Logger.LogDebug("Removing file: {name}", entry.FullName);
|
||||||
|
entry.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildTargetEntry = nugetPackage.Entries.FirstOrDefault(x =>
|
||||||
|
x.FullName == "build/Microsoft.AspNetCore.StaticWebAssets.props"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (buildTargetEntry == null)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Unable to find Microsoft.AspNetCore.StaticWebAssets.props to remove file references");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug("Removing file references");
|
||||||
|
|
||||||
|
await ModifyXmlInPackage(nugetPackage, buildTargetEntry,
|
||||||
|
document => document
|
||||||
|
.Descendants("StaticWebAsset")
|
||||||
|
.Where(x =>
|
||||||
|
{
|
||||||
|
var relativePath = x.Element("RelativePath")!.Value;
|
||||||
|
return relativePath.StartsWith(filter);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddSourceFiles(ZipArchive nugetPackage, string[] files, Func<string, string> buildPath)
|
||||||
|
{
|
||||||
|
foreach (var sourceFile in files)
|
||||||
|
{
|
||||||
|
var path = buildPath.Invoke(sourceFile);
|
||||||
|
|
||||||
|
Logger.LogDebug("Adding additional files as src: {path}", path);
|
||||||
|
|
||||||
|
await using var fs = File.Open(
|
||||||
|
sourceFile,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.ReadWrite
|
||||||
|
);
|
||||||
|
|
||||||
|
var entry = nugetPackage.CreateEntry(path);
|
||||||
|
await using var entryFs = entry.Open();
|
||||||
|
|
||||||
|
await fs.CopyToAsync(entryFs);
|
||||||
|
await entryFs.FlushAsync();
|
||||||
|
|
||||||
|
fs.Close();
|
||||||
|
entryFs.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Resources/Scripts/Models/CsprojManifest.cs
Normal file
9
Resources/Scripts/Models/CsprojManifest.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Scripts.Models;
|
||||||
|
|
||||||
|
public class CsprojManifest
|
||||||
|
{
|
||||||
|
public bool IsPackable { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
public string PackageId { get; set; }
|
||||||
|
public string[] PackageTags { get; set; }
|
||||||
|
}
|
||||||
8
Resources/Scripts/Models/NupkgManifest.cs
Normal file
8
Resources/Scripts/Models/NupkgManifest.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Scripts.Models;
|
||||||
|
|
||||||
|
public class NupkgManifest
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
public string[] Tags { get; set; }
|
||||||
|
}
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
using Scripts.Functions;
|
using Cocona;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MoonCore.Extensions;
|
||||||
|
using Scripts.Commands;
|
||||||
|
using Scripts.Helpers;
|
||||||
|
|
||||||
if (args.Length == 0)
|
Console.WriteLine("Moonlight Build Helper Script");
|
||||||
{
|
Console.WriteLine();
|
||||||
Console.WriteLine("You need to specify a module to run");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var module = args[0];
|
var builder = CoconaApp.CreateBuilder(args);
|
||||||
var moduleArgs = args.Skip(1).ToArray();
|
|
||||||
|
|
||||||
switch (module)
|
builder.Logging.ClearProviders();
|
||||||
{
|
builder.Logging.AddMoonCore();
|
||||||
case "staticWebAssets":
|
|
||||||
await StaticWebAssetsFunctions.Run(moduleArgs);
|
builder.Services.AddSingleton<CommandHelper>();
|
||||||
break;
|
builder.Services.AddSingleton<NupkgHelper>();
|
||||||
case "content":
|
builder.Services.AddSingleton<CsprojHelper>();
|
||||||
await ContentFunctions.Run(moduleArgs);
|
builder.Services.AddSingleton<CodeHelper>();
|
||||||
break;
|
|
||||||
case "src":
|
var app = builder.Build();
|
||||||
await SrcFunctions.Run(moduleArgs);
|
|
||||||
break;
|
app.AddCommands<PackCommand>();
|
||||||
default:
|
app.AddCommands<PreBuildCommand>();
|
||||||
Console.WriteLine($"No module named {module} found");
|
|
||||||
break;
|
await app.RunAsync();
|
||||||
}
|
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
</Project>
|
<PackageReference Include="Cocona" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.13.0" />
|
||||||
|
<PackageReference Include="MoonCore" Version="1.8.6" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user