Prepared tailwind system for plugin builds and exports via nuget. Removed obsolete old css bundling. Added helper scripts for building. Rewritten build scripts

This commit is contained in:
2025-05-11 00:07:41 +02:00
parent 1a67fcffb4
commit 1b4d32eed3
28 changed files with 424 additions and 519 deletions

6
.gitignore vendored
View File

@@ -428,4 +428,8 @@ core.min.css
# Build script for nuget packages # Build script for nuget packages
finalPackages/ finalPackages/
nupkgs/ nupkgs/
# Scripts
**/bin/**
**/obj/**

View File

@@ -10,6 +10,5 @@ public class PluginManifest
public string[] Scripts { get; set; } = []; public string[] Scripts { get; set; } = [];
public string[] Styles { get; set; } = []; public string[] Styles { get; set; } = [];
public string[] BundledStyles { get; set; } = [];
public Dictionary<string, string[]> Assemblies { get; set; } = new(); public Dictionary<string, string[]> Assemblies { get; set; } = new();
} }

View File

@@ -19,6 +19,7 @@
<ItemGroup> <ItemGroup>
<Content Include="..\.dockerignore"> <Content Include="..\.dockerignore">
<Link>.dockerignore</Link> <Link>.dockerignore</Link>
<Pack>false</Pack>
</Content> </Content>
</ItemGroup> </ItemGroup>

View File

@@ -1,183 +0,0 @@
using ExCSS;
using Microsoft.Extensions.FileProviders;
using MoonCore.Helpers;
namespace Moonlight.ApiServer.Services;
public class BundleGenerationService : IHostedService
{
private readonly ILogger<BundleGenerationService> Logger;
private readonly IWebHostEnvironment HostEnvironment;
private readonly PluginService PluginService;
private readonly BundleService BundleService;
public BundleGenerationService(
ILogger<BundleGenerationService> logger,
IWebHostEnvironment hostEnvironment,
BundleService bundleService,
PluginService pluginService
)
{
Logger = logger;
HostEnvironment = hostEnvironment;
BundleService = bundleService;
PluginService = pluginService;
}
private async Task Bundle(CancellationToken cancellationToken)
{
Logger.LogInformation("Bundling css files...");
// Search the physical path for the defined files
var physicalCssFiles = new List<string>();
foreach (var cssFile in BundleService.GetCssFiles())
{
var fileInfo = HostEnvironment.WebRootFileProvider.GetFileInfo(cssFile);
if (fileInfo is NotFoundFileInfo || fileInfo.PhysicalPath == null)
{
fileInfo = PluginService.WwwRootFileProvider.GetFileInfo(cssFile);
if (fileInfo is NotFoundFileInfo || fileInfo.PhysicalPath == null)
{
Logger.LogWarning(
"Unable to find physical path for the requested css file '{file}'. Make sure its inside a wwwroot folder",
cssFile
);
continue;
}
}
Logger.LogTrace("Discovered css file '{path}' at '{physicalPath}'", cssFile, fileInfo.PhysicalPath);
physicalCssFiles.Add(fileInfo.PhysicalPath);
}
if (physicalCssFiles.Count == 0)
Logger.LogWarning(
"No physical paths to css files loaded. The generated bundle will be empty. Unless this is intended by you this is a bug");
// TODO: Implement cache
// TODO: File system watcher for development
var bundleContent = await CreateCssBundle(physicalCssFiles);
Directory.CreateDirectory(PathBuilder.Dir("storage", "tmp"));
await File.WriteAllTextAsync(PathBuilder.File("storage", "tmp", "bundle.css"), bundleContent,
cancellationToken);
Logger.LogInformation("Successfully built css bundle");
}
private async Task<string> CreateCssBundle(List<string> physicalPaths)
{
if (physicalPaths.Count == 0) // No stylesheets defined => nothing to process
return "";
if (physicalPaths.Count == 1) // Only one stylesheet => nothing to process
return await File.ReadAllTextAsync(physicalPaths[0]);
// Simple bundler just to test
var result = "";
foreach (var path in physicalPaths)
{
result += await File.ReadAllTextAsync(path);
}
return result;
// Create bundle by stripping out double declared classes and combining all css files into one bundle
var parser = new StylesheetParser();
string? content = null;
Stylesheet? mainStylesheet = null;
var additionalStyleSheets = new List<Stylesheet>();
foreach (var physicalPath in physicalPaths)
{
try
{
var fileContent = await File.ReadAllTextAsync(physicalPath);
var stylesheet = await parser.ParseAsync(fileContent);
// Check if it's the first stylesheet we are loading
if (mainStylesheet == null || content == null)
{
// Delegate the first stylesheet to be the main one
content = fileContent + "\n";
mainStylesheet = stylesheet;
}
else
additionalStyleSheets.Add(stylesheet); // All other stylesheets are to be processed
}
catch (Exception e)
{
Logger.LogError("An error occured while parsing css file: {e}", e);
}
}
// Handle an empty main stylesheet delegation
if (mainStylesheet == null || content == null)
{
Logger.LogError("An unable to delegate main stylesheet. Did every load attempt of an stylesheet fail?");
return "";
}
// Process stylesheets against the main one
foreach (var stylesheet in additionalStyleSheets)
{
// Style
foreach (var styleRule in stylesheet.StyleRules)
{
if (mainStylesheet.StyleRules.Any(x => x.Selector.Text == styleRule.Selector.Text))
continue;
content += styleRule.StylesheetText.Text + "\n";
}
// Container
foreach (var containerRule in stylesheet.ContainerRules)
{
if (mainStylesheet.ContainerRules.Any(x => x.ConditionText == containerRule.ConditionText))
continue;
content += containerRule.StylesheetText.Text + "\n";
}
// Import Rule
foreach (var importRule in stylesheet.ImportRules)
{
if (mainStylesheet.ImportRules.Any(x => x.Text == importRule.Text))
continue;
content = importRule.StylesheetText.Text + "\n" + content;
}
// Media Rules
foreach (var mediaRule in stylesheet.MediaRules)
content += mediaRule.StylesheetText.Text + "\n";
// Page Rules
foreach (var pageRule in stylesheet.PageRules)
{
if (mainStylesheet.PageRules.Any(x => x.SelectorText == pageRule.SelectorText))
continue;
content += pageRule.StylesheetText.Text + "\n";
}
}
return content;
}
//
public Task StartAsync(CancellationToken cancellationToken)
=> Bundle(cancellationToken);
public Task StopAsync(CancellationToken cancellationToken)
=> Task.CompletedTask;
}

View File

@@ -1,14 +0,0 @@
namespace Moonlight.ApiServer.Services;
public class BundleService
{
private readonly List<string> CssFiles = new();
public void BundleCss(string path)
=> CssFiles.Add(path);
public void BundleCssRange(string[] paths)
=> CssFiles.AddRange(paths);
public IEnumerable<string> GetCssFiles() => CssFiles;
}

View File

@@ -54,9 +54,6 @@ public class Startup
private PluginService PluginService; private PluginService PluginService;
private AssemblyLoadContext PluginLoadContext; private AssemblyLoadContext PluginLoadContext;
// Asset bundling
private BundleService BundleService = new();
private IPluginStartup[] PluginStartups; private IPluginStartup[] PluginStartups;
public async Task Run(string[] args, Assembly[]? additionalAssemblies = null, public async Task Run(string[] args, Assembly[]? additionalAssemblies = null,
@@ -71,7 +68,6 @@ public class Startup
await CreateStorage(); await CreateStorage();
await SetupAppConfiguration(); await SetupAppConfiguration();
await SetupLogging(); await SetupLogging();
await SetupBundling();
await LoadPlugins(); await LoadPlugins();
await InitializePlugins(); await InitializePlugins();
@@ -132,15 +128,6 @@ public class Startup
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task SetupBundling()
{
BundleService = new();
BundleService.BundleCss("css/core.min.css");
return Task.CompletedTask;
}
#region Base #region Base
private Task RegisterBase() private Task RegisterBase()
@@ -257,13 +244,6 @@ public class Startup
// Configure base services for initialisation // Configure base services for initialisation
startupSc.AddSingleton(Configuration); startupSc.AddSingleton(Configuration);
// Add bundle service so plugins can do additional bundling if required
startupSc.AddSingleton(BundleService);
// Auto add all files specified in the bundledStyles section to the bundle job
foreach (var plugin in PluginService.LoadedPlugins.Keys)
BundleService.BundleCssRange(plugin.BundledStyles);
startupSc.AddLogging(builder => startupSc.AddLogging(builder =>
{ {
builder.ClearProviders(); builder.ClearProviders();
@@ -308,10 +288,6 @@ public class Startup
private Task RegisterPluginAssets() private Task RegisterPluginAssets()
{ {
WebApplicationBuilder.Services.AddHostedService(sp => sp.GetRequiredService<BundleGenerationService>());
WebApplicationBuilder.Services.AddSingleton<BundleGenerationService>();
WebApplicationBuilder.Services.AddSingleton(BundleService);
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -9,6 +9,7 @@
<DefaultItemExcludes> <DefaultItemExcludes>
**\bin\**;**\obj\**;**\node_modules\**;**\Styles\*.json **\bin\**;**\obj\**;**\node_modules\**;**\Styles\*.json
</DefaultItemExcludes> </DefaultItemExcludes>
<StaticWebAssetsEnabled>True</StaticWebAssetsEnabled>
</PropertyGroup> </PropertyGroup>
@@ -40,11 +41,6 @@
<PackagePath>src</PackagePath> <PackagePath>src</PackagePath>
<CopyToOutputDirectory>Never</CopyToOutputDirectory> <CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None> </None>
<None Include="**\*.razor" Exclude="storage\**\*;bin\**\*;obj\**\*">
<Pack>true</Pack>
<PackagePath>src</PackagePath>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Include="Styles\**\*" Exclude="storage\**\*;bin\**\*;obj\**\*;Styles\node_modules\**\*"> <None Include="Styles\**\*" Exclude="storage\**\*;bin\**\*;obj\**\*;Styles\node_modules\**\*">
<Pack>true</Pack> <Pack>true</Pack>
<PackagePath>styles</PackagePath> <PackagePath>styles</PackagePath>
@@ -72,10 +68,4 @@
<ProjectReference Include="..\Moonlight.Shared\Moonlight.Shared.csproj"/> <ProjectReference Include="..\Moonlight.Shared\Moonlight.Shared.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Helpers\"/>
<Folder Include="Styles\"/>
<Folder Include="wwwroot\css\"/>
</ItemGroup>
</Project> </Project>

View File

@@ -1 +0,0 @@
npx tailwindcss -i style.css -o ../wwwroot/css/core.min.css --watch

View File

@@ -1,2 +0,0 @@
#! /bin/bash
npx tailwindcss -i style.css -o ../wwwroot/css/core.min.css --watch

View File

@@ -0,0 +1,26 @@
@import "./additions/fonts.css";
@import "./additions/theme.css" layer(theme);
/* @import "./additions/theme.white.css"; */
@import "./additions/buttons.css" layer(components);
@import "./additions/cards.css" layer(components);
@import "./additions/forms.css" layer(utilities);
@import "./additions/progress.css" layer(components);
@import "./additions/scrollbar.css" layer(components);
@import "./additions/loaders.css" layer(components);
@import "./additions/tabs.css" layer(components);
@source "./mappings/*.map";
#blazor-error-ui {
display: none;
}
#blazor-loader-label:after {
content: var(--blazor-load-percentage-text, "Loading");
}
#blazor-loader-progress {
width: var(--blazor-load-percentage, 0%);
}

View File

@@ -7,7 +7,8 @@
"dependencies": { "dependencies": {
"@tailwindcss/cli": "^4.1.4", "@tailwindcss/cli": "^4.1.4",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"tailwindcss": "^4.1.4" "tailwindcss": "^4.1.4",
"xml2js": "^0.6.2"
} }
}, },
"node_modules/@parcel/watcher": { "node_modules/@parcel/watcher": {
@@ -904,6 +905,11 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "4.1.5", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz",
@@ -927,6 +933,26 @@
"engines": { "engines": {
"node": ">=8.0" "node": ">=8.0"
} }
},
"node_modules/xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"engines": {
"node": ">=4.0"
}
} }
} }
} }

View File

@@ -2,6 +2,11 @@
"dependencies": { "dependencies": {
"@tailwindcss/cli": "^4.1.4", "@tailwindcss/cli": "^4.1.4",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"tailwindcss": "^4.1.4" "tailwindcss": "^4.1.4",
"xml2js": "^0.6.2"
},
"scripts": {
"tailwind": "npx tailwindcss -i style.css -o ../wwwroot/css/style.min.css --watch",
"pretailwind": "node resolveNuget.js ../Moonlight.Client.csproj"
} }
} }

View File

@@ -0,0 +1 @@
@import "./additions/fonts.css";

View File

@@ -0,0 +1,80 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const xml2js = require('xml2js');
// Helpers
function getPackageRefs(csprojPath) {
const xml = fs.readFileSync(csprojPath, 'utf8');
const parser = new xml2js.Parser();
return new Promise((resolve, reject) => {
parser.parseString(xml, (err, result) => {
if (err) return reject(err);
const itemGroups = result.Project.ItemGroup || [];
const refs = [];
for (const group of itemGroups) {
const packages = group.PackageReference || [];
for (const pkg of packages) {
const name = pkg.$.Include;
const version = pkg.$.Version || (pkg.Version && pkg.Version[0]);
if (name && version) {
refs.push({ name: name.toLowerCase(), version });
}
}
}
resolve(refs);
});
});
}
async function main() {
const csprojPath = process.argv[2];
if (!csprojPath || !fs.existsSync(csprojPath)) {
console.error('Usage: Missing csproj path');
process.exit(1);
}
const nugetPath = path.join(os.homedir(), '.nuget', 'packages');
const moonlightDir = path.join(__dirname, 'node_modules', 'moonlight');
fs.mkdirSync(moonlightDir, { recursive: true });
const refs = await getPackageRefs(csprojPath);
var outputCss = "";
var preOutputCss = "";
for (const { name, version } of refs) {
const packagePath = path.join(nugetPath, name, version);
const exportsFile = path.join(packagePath, 'styles', 'exports.css');
const preTailwindFile = path.join(packagePath, 'styles', 'preTailwind.css');
const sourceFolder = path.join(packagePath, 'src');
const rel = (p) => p.replace(/\\/g, '/');
if (fs.existsSync(exportsFile)) {
outputCss += `@import "${rel(exportsFile)}";\n`;
}
if (fs.existsSync(preTailwindFile)) {
preOutputCss += `@import "${rel(preTailwindFile)}";\n`;
}
if (fs.existsSync(sourceFolder)) {
outputCss += `@source "${rel(path.join(sourceFolder, "**", "*.razor"))}";\n`;
outputCss += `@source "${rel(path.join(sourceFolder, "**", "*.cs"))}";\n`;
outputCss += `@source "${rel(path.join(sourceFolder, "**", "*.html"))}";\n`;
}
}
fs.writeFileSync(path.join(moonlightDir, 'nuget.css'), outputCss);
fs.writeFileSync(path.join(moonlightDir, 'preTailwind.nuget.css'), preOutputCss);
console.log(`Generated nuget.css in ${moonlightDir}`);
}
main().catch(err => {
console.error(err);
process.exit(1);
});

View File

@@ -1,32 +1,14 @@
@import "./additions/fonts.css"; @import "./preTailwind.css";
@import "moonlight/preTailwind.nuget.css";
@import "tailwindcss"; @import "tailwindcss";
@import "./additions/theme.css" layer(theme);
/* @import "./additions/theme.white.css"; */ @import "./exports.css";
@import "moonlight/nuget.css";
@import "./additions/buttons.css" layer(components);
@import "./additions/cards.css" layer(components);
@import "./additions/forms.css" layer(utilities);
@import "./additions/progress.css" layer(components);
@import "./additions/scrollbar.css" layer(components);
@import "./additions/loaders.css" layer(components);
@import "./additions/tabs.css" layer(components);
@plugin "@tailwindcss/forms"; @plugin "@tailwindcss/forms";
@source "../**/*.razor"; @source "../**/*.razor";
@source "../**/*.cs"; @source "../**/*.cs";
@source "../**/*.html"; @source "../**/*.html";
@source "./mappings/*.map"; @source "./mappings/*.map";
#blazor-error-ui {
display: none;
}
#blazor-loader-label:after {
content: var(--blazor-load-percentage-text, "Loading");
}
#blazor-loader-progress {
width: var(--blazor-load-percentage, 0%);
}

View File

@@ -6,10 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Moonlight.Client</title> <title>Moonlight.Client</title>
<base href="/" /> <base href="/" />
<link rel="stylesheet" href="/css/bundle.css" /> <link rel="stylesheet" href="/css/style.min.css" />
<link href="manifest.webmanifest" rel="manifest" /> <link href="manifest.webmanifest" rel="manifest" />
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" /> <link rel="apple-touch-icon" sizes="512x512" href="/img/icon-512.png" />
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" /> <link rel="apple-touch-icon" sizes="192x192" href="/img/icon-192.png" />
</head> </head>
<body class="bg-gray-950 text-white font-inter h-full"> <body class="bg-gray-950 text-white font-inter h-full">

View File

@@ -0,0 +1,60 @@
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();
}
}

View File

@@ -0,0 +1,47 @@
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();
}
}
}

View File

@@ -0,0 +1,93 @@
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();
}
}

View File

@@ -0,0 +1,26 @@
using Scripts.Functions;
if (args.Length == 0)
{
Console.WriteLine("You need to specify a module to run");
return;
}
var module = args[0];
var moduleArgs = args.Skip(1).ToArray();
switch (module)
{
case "staticWebAssets":
await StaticWebAssetsFunctions.Run(moduleArgs);
break;
case "content":
await ContentFunctions.Run(moduleArgs);
break;
case "src":
await SrcFunctions.Run(moduleArgs);
break;
default:
Console.WriteLine($"No module named {module} found");
break;
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -1,97 +0,0 @@
# Set strict mode to stop on errors
$ErrorActionPreference = "Stop"
# Ensure the script is running in the main directory
Write-Host "Building nuget packages"
Write-Host "Searching & building project files"
# Find all .csproj files recursively
$projectFiles = Get-ChildItem -Recurse -Filter "*.csproj"
foreach ($project in $projectFiles) {
# Extract project name (without extension)
$projectName = $project.BaseName
# Extract version from the .csproj file
$projectVersion = Select-String -Path $project.FullName -Pattern "<Version>(.*?)</Version>" | ForEach-Object {
$_.Matches.Groups[1].Value
}
if (-not $projectVersion) {
Write-Host "No <Version> tag found in $($project.FullName), skipping."
continue
}
# Build and pack the project
$projectPath = $project.DirectoryName
$pwd = (Get-Location).Path
Push-Location $projectPath
dotnet build --configuration Release
dotnet pack --configuration Release --output "$pwd\nupkgs"
Pop-Location
# Modifying the NuGet package
Write-Host "Modding nuget package"
$nugetPackage = Get-ChildItem "$pwd\nupkgs" -Filter "*.nupkg" | Select-Object -First 1
# Rename .nupkg to .zip
$zipPackage = "$($nugetPackage.FullName).zip"
Rename-Item -Path $nugetPackage.FullName -NewName $zipPackage
Expand-Archive -Path $zipPackage -DestinationPath "$pwd\nupkgs\mod" -Force
if ($projectName -eq "Moonlight.ApiServer") {
Remove-Item "$pwd\nupkgs\mod\content" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item "$pwd\nupkgs\mod\contentFiles" -Recurse -Force -ErrorAction SilentlyContinue
$xmlFilePath = "$pwd\nupkgs\mod\$projectName.nuspec"
$xmlContent = Get-Content -Path $xmlFilePath -Raw
$regexPattern = '<contentFiles\b[^>]*>[\s\S]*?<\/contentFiles>'
$updatedContent = [System.Text.RegularExpressions.Regex]::Replace(
$xmlContent,
$regexPattern,
"",
[System.Text.RegularExpressions.RegexOptions]::IgnoreCase
)
Set-Content -Path $xmlFilePath -Value $updatedContent -Encoding UTF8
}
if ($projectName -eq "Moonlight.Client") {
Remove-Item "$pwd\nupkgs\mod\staticwebassets\_framework" -Recurse -Force
$xmlFilePath = "$pwd\nupkgs\mod\build\Microsoft.AspNetCore.StaticWebAssets.props"
$xmlContent = Get-Content -Path $xmlFilePath -Raw
$regexPattern = '<StaticWebAsset\b[^>]*>(?:(?!<\/StaticWebAsset>).)*?<RelativePath>_framework/blazor\.webassembly\.js(?:\.gz)?<\/RelativePath>.*?<\/StaticWebAsset>'
$updatedContent = [System.Text.RegularExpressions.Regex]::Replace(
$xmlContent,
$regexPattern,
"",
[System.Text.RegularExpressions.RegexOptions]::Singleline
)
Set-Content -Path $xmlFilePath -Value $updatedContent -Encoding UTF8
}
# Repack the modified NuGet package
Write-Host "Repacking nuget package"
Remove-Item $zipPackage
Push-Location "$pwd\nupkgs\mod"
Compress-Archive -Path * -DestinationPath $zipPackage -Force
Pop-Location
# Rename .zip back to .nupkg
Rename-Item -Path $zipPackage -NewName $nugetPackage.FullName
# Move the final package to the output directory
$finalDir = "$pwd\finalPackages"
New-Item -ItemType Directory -Path $finalDir -Force | Out-Null
Move-Item -Path $nugetPackage.FullName -Destination $finalDir
# Cleanup
Write-Host "Cleaning up"
Remove-Item "$pwd\nupkgs\mod" -Recurse -Force
}

View File

@@ -1,58 +0,0 @@
#!/bin/bash
set -e
# Note: Run in main directory, i.e. where the Moonlight.sln is
echo "Building nuget packages"
echo "Searching & building project files"
project_files=$(find . -name "*.csproj")
for project in $project_files; do
# Extract project name
project_name=$(basename "$project" .csproj)
# Extract version
project_version=$(grep -oPm1 "(?<=<Version>)[^<]+" "$project")
if [ -z "$project_version" ]; then
echo "No <Version> tag found in $project, skipping."
continue
fi
# Building nuget package
pwd=$(pwd)
project_path=$(dirname $project)
(cd $project_path; dotnet build --configuration Release; dotnet pack --configuration Release --output $pwd/nupkgs)
# Mod nuget package
echo "Modding nuget package"
nugetPackage=$(find $pwd/nupkgs -name "*.nupkg")
unzip -o $nugetPackage -d $pwd/nupkgs/mod
if [ "$project_name" = "Moonlight.ApiServer" ]; then
rm -r $pwd/nupkgs/mod/content
rm -r $pwd/nupkgs/mod/contentFiles
sed -i "/<contentFiles>/,/<\/contentFiles>/d" $pwd/nupkgs/mod/Moonlight.ApiServer.nuspec
fi
if [ "$project_name" = "Moonlight.Client" ]; then
rm -r $pwd/nupkgs/mod/staticwebassets/_framework
sed -i '/<StaticWebAsset Include=.*blazor.webassembly.js.gz.*>/,/<\/StaticWebAsset>/d' $pwd/nupkgs/mod/build/Microsoft.AspNetCore.StaticWebAssets.props
sed -i '/<StaticWebAsset Include=.*blazor.webassembly.js.*>/,/<\/StaticWebAsset>/d' $pwd/nupkgs/mod/build/Microsoft.AspNetCore.StaticWebAssets.props
fi
echo "Repacking nuget package"
rm $nugetPackage
(cd nupkgs/mod/; zip -r -o $nugetPackage *)
mkdir -p $pwd/finalPackages/
mv $nugetPackage $pwd/finalPackages/
echo "Cleaning up"
rm -r $pwd/nupkgs/mod
done

View File

@@ -0,0 +1,25 @@
#!/bin/bash
# We are building the packages in the debug mode because they are meant for development
# purposes only. When build for production, release builds will be used ofc
set -e
echo "Creating nuget packages"
mkdir -p finalPackages
echo "+ ApiServer Server"
dotnet build -c Debug Moonlight.ApiServer
dotnet pack -c Debug Moonlight.ApiServer --output finalPackages/
dotnet run --project Resources/Scripts/Scripts.csproj content finalPackages/Moonlight.ApiServer.2.1.0.nupkg ".*"
echo "+ Client"
dotnet build -c Debug Moonlight.Client
dotnet pack -c Debug Moonlight.Client --output finalPackages/
dotnet run --project Resources/Scripts/Scripts.csproj staticWebAssets finalPackages/Moonlight.Client.2.1.0.nupkg "_framework\/.*" style.min.css
dotnet run --project Resources/Scripts/Scripts.csproj src finalPackages/Moonlight.Client.2.1.0.nupkg Moonlight.Client/ *.razor
dotnet run --project Resources/Scripts/Scripts.csproj src finalPackages/Moonlight.Client.2.1.0.nupkg Moonlight.Client/ wwwroot/*.html
echo "+ Shared library"
dotnet build -c Debug Moonlight.Shared
dotnet pack -c Debug Moonlight.Shared --output finalPackages/

View File

@@ -1,66 +0,0 @@
using System.IO.Compression;
using System.Text;
using System.Xml.Linq;
// Handle arguments
if (Args.Count != 1)
{
Console.WriteLine("You need to provide the path to a nuget file as a parameter");
return;
}
var nugetPath = Args[0];
// Check if file exists
if (!File.Exists(nugetPath))
{
Console.WriteLine("The provided file does not exist");
return;
}
// Open file to modify
Console.WriteLine($"Modding nuget package: {nugetPath}");
var zipFile = ZipFile.Open(nugetPath, ZipArchiveMode.Update, Encoding.UTF8);
// First we want to remove the framework files
Console.WriteLine("Removing framework files");
var frameworkEntries = zipFile.Entries
.Where(x => x.FullName.Contains("staticwebassets/_framework"))
.ToArray();
foreach (var frameworkEntry in frameworkEntries)
frameworkEntry.Delete();
// Then we want to modify the build targets for static web assets
var oldBuildTarget = zipFile.Entries
.First(x => x.FullName == "build/Microsoft.AspNetCore.StaticWebAssets.props");
// Load old content
var oldContentStream = oldBuildTarget.Open();
// Parse xml and remove framework references
Console.WriteLine("Removing framework web asset references");
var contentXml = XDocument.Load(oldContentStream);
oldContentStream.Close();
oldContentStream.Dispose();
oldBuildTarget.Delete();
var assetsToRemove = contentXml
.Descendants("StaticWebAsset")
.Where(asset =>
asset.Element("RelativePath")?.Value.Contains("_framework") == true)
.ToArray();
foreach (var asset in assetsToRemove)
asset.Remove();
var newBuildTarget = zipFile.CreateEntry("build/Microsoft.AspNetCore.StaticWebAssets.props");
var newContentStream = newBuildTarget.Open();
contentXml.Save(newContentStream);
await newContentStream.FlushAsync();
newContentStream.Close();
zipFile.Dispose();

View File

@@ -1,15 +0,0 @@
# This script requires a NuGet override folder at %userprofile%\NugetOverride
# Clear old build cache
Remove-Item -Recurse -Force nupkgs, finalPackages
# Build and replace NuGet packages
& "Resources\Scripts\buildNuget.ps1"
Copy-Item -Path finalPackages\* -Destination $env:userprofile\NugetOverride -Force
# Clean package cache
Remove-Item -Recurse -Force $env:userprofile\.nuget\packages\moonlight.apiserver
Remove-Item -Recurse -Force $env:userprofile\.nuget\packages\moonlight.shared
Remove-Item -Recurse -Force $env:userprofile\.nuget\packages\moonlight.client
Write-Output "Done :>"

View File

@@ -1,17 +0,0 @@
#!/bin/bash
# This script required a nuget override folder at ~/NugetOverride
# Clear old build cache
rm -rf nupkgs/ finalPackages/
# Build and replace nuget packages
bash Resources/Scripts/buildNuget.sh
cp finalPackages/* ~/NugetOverride/
# Clean package cache
rm -rf ~/.nuget/packages/moonlight.apiserver/
rm -rf ~/.nuget/packages/moonlight.shared/
rm -rf ~/.nuget/packages/moonlight.client/
echo "Done :>"

View File

@@ -0,0 +1,7 @@
#!/bin/bash
bash Resources/Scripts/generateNuget.sh
echo "+ Copying to nuget override"
cp finalPackages/*.nupkg ~/NugetOverride/
rm -r ~/.nuget/packages/moonlight.*