From 4e20e1c325449e655823e8b922e330d4182da3c2 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Sun, 1 Jun 2025 16:36:03 +0300 Subject: [PATCH 1/2] Aspirify local dev --- source/AppHost/AppHost.cs | 30 ++++ source/AppHost/AppHost.csproj | 24 ++++ source/AppHost/Readme.md | 13 ++ source/AppHost/appsettings.Development.json | 8 ++ source/AppHost/appsettings.json | 9 ++ .../Container.Manager.csproj | 1 + .../Internal/ExecutionManager.cs | 7 + source/Container.Manager/Program.cs | 18 ++- .../DirectoryModuleRegistrar.cs | 85 ++++++++++++ .../IDirectoryModuleRegistrar.cs | 13 ++ .../ImmediateModuleRegistrar.cs | 29 ++++ .../RegistrationExtensions.cs | 22 +++ source/Server/Caching/CachingModule.cs | 2 +- .../Server/Integration/Azure/AzureModule.cs | 4 +- source/Server/Program.cs | 22 ++- source/Server/Server.csproj | 5 +- source/ServiceDefaults/Extensions.cs | 129 ++++++++++++++++++ source/ServiceDefaults/ServiceDefaults.csproj | 22 +++ source/ServiceDefaults/SharpLabActivities.cs | 7 + source/SharpLab.sln | 40 ++++++ source/WebApp.Server/Program.cs | 22 ++- source/WebApp.Server/WebApp.Server.csproj | 3 +- source/WebApp/package.json | 1 + source/WebApp/scripts.ts | 6 +- 24 files changed, 494 insertions(+), 28 deletions(-) create mode 100644 source/AppHost/AppHost.cs create mode 100644 source/AppHost/AppHost.csproj create mode 100644 source/AppHost/Readme.md create mode 100644 source/AppHost/appsettings.Development.json create mode 100644 source/AppHost/appsettings.json create mode 100644 source/Server/Autofac.Extras.FileSystemRegistration/DirectoryModuleRegistrar.cs create mode 100644 source/Server/Autofac.Extras.FileSystemRegistration/IDirectoryModuleRegistrar.cs create mode 100644 source/Server/Autofac.Extras.FileSystemRegistration/ImmediateModuleRegistrar.cs create mode 100644 source/Server/Autofac.Extras.FileSystemRegistration/RegistrationExtensions.cs create mode 100644 source/ServiceDefaults/Extensions.cs create mode 100644 source/ServiceDefaults/ServiceDefaults.csproj create mode 100644 source/ServiceDefaults/SharpLabActivities.cs diff --git a/source/AppHost/AppHost.cs b/source/AppHost/AppHost.cs new file mode 100644 index 000000000..da257ff65 --- /dev/null +++ b/source/AppHost/AppHost.cs @@ -0,0 +1,30 @@ +using System.Numerics; + +var builder = DistributedApplication.CreateBuilder(args); + +var webappPort = builder.AddParameter("webapp-port", "44200"); + +var storage = builder.AddAzureStorage("storage") + .RunAsEmulator(options => options.WithLifetime(ContainerLifetime.Persistent).WithBlobPort(10000)); +var storageBlob = storage.AddBlobs("cache"); +var blobContainer = storageBlob.AddBlobContainer("edge"); + +var containerManager = builder.AddProject("container-manager"); + +var installWebApp = builder.AddExecutable("install-webapp", "npm", "../WebApp", "install"); +var buildWebApp = builder.AddExecutable("build-webapp", "npm", "../WebApp", "run", "build") + .WithParentRelationship(installWebApp) + .WaitForCompletion(installWebApp); +var webapp = builder.AddNpmApp("webapp", "../WebApp", scriptName: "start:fast") + .WaitForCompletion(buildWebApp) + .WithHttpEndpoint(env: "PORT") + .WithEnvironment("PORT", webappPort) + .WithEnvironment("test", blobContainer).WaitFor(blobContainer); +installWebApp.WithParentRelationship(webapp); + +var webappServer = builder.AddProject("webapp-server") + .WaitFor(webapp) + .WaitFor(containerManager) + .WithEnvironment("SHARPLAB_LOCAL_SECRETS_PublicStorageConnectionString", storageBlob).WaitFor(storageBlob); + +builder.Build().Run(); diff --git a/source/AppHost/AppHost.csproj b/source/AppHost/AppHost.csproj new file mode 100644 index 000000000..9f568b467 --- /dev/null +++ b/source/AppHost/AppHost.csproj @@ -0,0 +1,24 @@ + + + + + + Exe + net9.0 + enable + enable + 9cc6fba6-3727-4501-b54b-d61cf8eae623 + + + + + + + + + + + + + + diff --git a/source/AppHost/Readme.md b/source/AppHost/Readme.md new file mode 100644 index 000000000..a7088da95 --- /dev/null +++ b/source/AppHost/Readme.md @@ -0,0 +1,13 @@ +# Aspire Setup + +run: + +```bash +git submodule update --init --recursive +``` + +# Aspire AppHost + +```bash +dotnet run --project source/AppHost +``` \ No newline at end of file diff --git a/source/AppHost/appsettings.Development.json b/source/AppHost/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/source/AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/source/AppHost/appsettings.json b/source/AppHost/appsettings.json new file mode 100644 index 000000000..31c092aa4 --- /dev/null +++ b/source/AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/source/Container.Manager/Container.Manager.csproj b/source/Container.Manager/Container.Manager.csproj index 963265039..9189abaec 100644 --- a/source/Container.Manager/Container.Manager.csproj +++ b/source/Container.Manager/Container.Manager.csproj @@ -21,6 +21,7 @@ + diff --git a/source/Container.Manager/Internal/ExecutionManager.cs b/source/Container.Manager/Internal/ExecutionManager.cs index ed42edc62..9f845b156 100644 --- a/source/Container.Manager/Internal/ExecutionManager.cs +++ b/source/Container.Manager/Internal/ExecutionManager.cs @@ -1,6 +1,8 @@ using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; namespace SharpLab.Container.Manager.Internal { public class ExecutionManager { @@ -30,6 +32,7 @@ CancellationToken cancellationToken ) { // Note that _containers are never accessed through multiple threads for the same session id, // so atomicity is not required within same session id + using var activity = SharpLabActivities.Source.StartActivity("Container Execution", ActivityKind.Internal); using var allocationCancellation = CancellationFactory.ContainerAllocation(cancellationToken); if (_containerPool.GetSessionContainer(sessionId) is not {} container) { if (_crashSuspensionManager.GetSuspension(sessionId) is {} suspension) @@ -51,6 +54,10 @@ CancellationToken cancellationToken cancellationToken ); + if (!result.IsSuccess) { + activity?.SetStatus(ActivityStatusCode.Error, result.FailureMessage.ToString()); + } + if (container.HasExited()) return RemoveContainerAndSetSuspension(sessionId, result); diff --git a/source/Container.Manager/Program.cs b/source/Container.Manager/Program.cs index d5f0ef6b4..d55f89e8b 100644 --- a/source/Container.Manager/Program.cs +++ b/source/Container.Manager/Program.cs @@ -1,5 +1,5 @@ using System.Runtime.Versioning; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Hosting; using SharpLab.Container.Manager.Internal; @@ -8,11 +8,17 @@ namespace SharpLab.Container.Manager { public class Program { public static void Main(string[] args) { DotEnv.Load(); - CreateHostBuilder(args).Build().Run(); - } - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + var builder = WebApplication.CreateBuilder(args); + builder.AddServiceDefaults(); + + var startup = new Startup(); + startup.ConfigureServices(builder.Services); + + var app = builder.Build(); + app.MapDefaultEndpoints(); + startup.Configure(app); + app.Run(); + } } } diff --git a/source/Server/Autofac.Extras.FileSystemRegistration/DirectoryModuleRegistrar.cs b/source/Server/Autofac.Extras.FileSystemRegistration/DirectoryModuleRegistrar.cs new file mode 100644 index 000000000..2a264f76b --- /dev/null +++ b/source/Server/Autofac.Extras.FileSystemRegistration/DirectoryModuleRegistrar.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using Autofac.Core; +using Autofac.Core.Registration; +using JetBrains.Annotations; + +namespace Autofac.Extras.FileSystemRegistration.Internal { + public class DirectoryModuleRegistrar : IDirectoryModuleRegistrar { + private readonly ContainerBuilder _builder; + private readonly string[] _directoryPaths; + + private string _filePattern = "*.*"; + private Func _fileFilter = f => f.Extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase) + || f.Extension.Equals(".exe", StringComparison.InvariantCultureIgnoreCase); + private Func _assemblyFilter = a => true; + + public DirectoryModuleRegistrar([NotNull] ContainerBuilder builder, params string[] directoryPaths) + { + ArgumentNullException.ThrowIfNull(builder); + _builder = builder; + _directoryPaths = directoryPaths; + + var callback = _builder.RegisterCallback(DiscoverModules); + RegistrarData = new ModuleRegistrarData(callback); + } + + public ModuleRegistrarData RegistrarData { get; } + + public IDirectoryModuleRegistrar WhereFileMatches(string filePattern) + { + ArgumentNullException.ThrowIfNull(filePattern); + + _filePattern = filePattern; + return this; + } + + public IDirectoryModuleRegistrar WhereFile(Func filter) { + ArgumentNullException.ThrowIfNull(filter); + + _fileFilter = filter; + return this; + } + + public IDirectoryModuleRegistrar WhereAssembly(Func filter) { + ArgumentNullException.ThrowIfNull(filter); + + _assemblyFilter = filter; + return this; + } + + private void DiscoverModules(IComponentRegistryBuilder registry) { + var files = _directoryPaths.Select(p => new DirectoryInfo(p)) + .SelectMany(d => d.GetFiles(_filePattern)) + .Where(_fileFilter); + + var registrar = new ImmediateModuleRegistrar(registry, this.RegistrarData); + foreach (var file in files) { + var assembly = LoadAssemblySafe(file); + if (assembly == null || !_assemblyFilter(assembly)) + continue; + + registrar.RegisterAssemblyModules(assembly); + } + } + + [CanBeNull] + private static Assembly? LoadAssemblySafe(FileInfo file) { + try { + return Assembly.LoadFrom(file.FullName); + } + catch (BadImageFormatException) { + return null; + } + } + + [NotNull] + public IModuleRegistrar RegisterModule([NotNull] IModule module) { + ArgumentNullException.ThrowIfNull(module); + _builder.RegisterCallback(module.Configure); + return this; + } + } +} \ No newline at end of file diff --git a/source/Server/Autofac.Extras.FileSystemRegistration/IDirectoryModuleRegistrar.cs b/source/Server/Autofac.Extras.FileSystemRegistration/IDirectoryModuleRegistrar.cs new file mode 100644 index 000000000..091a8f44b --- /dev/null +++ b/source/Server/Autofac.Extras.FileSystemRegistration/IDirectoryModuleRegistrar.cs @@ -0,0 +1,13 @@ +using System; +using System.IO; +using System.Reflection; +using Autofac.Core.Registration; +using JetBrains.Annotations; + +namespace Autofac.Extras.FileSystemRegistration.Internal { + public interface IDirectoryModuleRegistrar : IModuleRegistrar { + [NotNull] IDirectoryModuleRegistrar WhereFileMatches([NotNull] string filePattern); + [NotNull] IDirectoryModuleRegistrar WhereFile([NotNull] Func filter); + [NotNull] IDirectoryModuleRegistrar WhereAssembly([NotNull] Func filter); + } +} \ No newline at end of file diff --git a/source/Server/Autofac.Extras.FileSystemRegistration/ImmediateModuleRegistrar.cs b/source/Server/Autofac.Extras.FileSystemRegistration/ImmediateModuleRegistrar.cs new file mode 100644 index 000000000..5374fbea3 --- /dev/null +++ b/source/Server/Autofac.Extras.FileSystemRegistration/ImmediateModuleRegistrar.cs @@ -0,0 +1,29 @@ +using System; +using Autofac.Core; +using Autofac.Core.Registration; +using JetBrains.Annotations; + +namespace Autofac.Extras.FileSystemRegistration.Internal +{ + public class ImmediateModuleRegistrar : IModuleRegistrar + { + private readonly IComponentRegistryBuilder _registry; + + public ImmediateModuleRegistrar([NotNull] IComponentRegistryBuilder registry, ModuleRegistrarData registrarData) + { + ArgumentNullException.ThrowIfNull(registry); + ArgumentNullException.ThrowIfNull(registrarData); + + _registry = registry; + RegistrarData = registrarData; + } + + public ModuleRegistrarData RegistrarData { get; } + + public IModuleRegistrar RegisterModule(IModule module) + { + module.Configure(_registry); + return this; + } + } +} \ No newline at end of file diff --git a/source/Server/Autofac.Extras.FileSystemRegistration/RegistrationExtensions.cs b/source/Server/Autofac.Extras.FileSystemRegistration/RegistrationExtensions.cs new file mode 100644 index 000000000..5948bb2c8 --- /dev/null +++ b/source/Server/Autofac.Extras.FileSystemRegistration/RegistrationExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; +using System.Reflection; +using Autofac.Extras.FileSystemRegistration.Internal; +using JetBrains.Annotations; + +namespace Autofac.Extras.FileSystemRegistration { + [PublicAPI] + public static class RegistrationExtensions { + [PublicAPI, NotNull] + public static IDirectoryModuleRegistrar RegisterAssemblyModulesInDirectories([NotNull] this ContainerBuilder builder, params string[] directoryPaths) { + return new DirectoryModuleRegistrar(builder, directoryPaths); + } + + [PublicAPI, NotNull] + public static IDirectoryModuleRegistrar RegisterAssemblyModulesInDirectoryOf([NotNull] this ContainerBuilder builder, Assembly assembly) { + if (assembly == null) throw new ArgumentNullException("assembly"); + var directoryPath = Path.GetDirectoryName(new Uri(assembly.Location).LocalPath); + return builder.RegisterAssemblyModulesInDirectories(directoryPath!); + } + } +} \ No newline at end of file diff --git a/source/Server/Caching/CachingModule.cs b/source/Server/Caching/CachingModule.cs index 6cc682e94..d7330790b 100644 --- a/source/Server/Caching/CachingModule.cs +++ b/source/Server/Caching/CachingModule.cs @@ -9,7 +9,7 @@ namespace SharpLab.Server.Caching; public class CachingModule : Module { protected override void Load(ContainerBuilder builder) { var webAppName = EnvironmentHelper.GetRequiredEnvironmentVariable("SHARPLAB_WEBAPP_NAME"); - var branchId = webAppName.StartsWith("sl-") ? webAppName : null; + var branchId = webAppName.StartsWith("sl-") ? webAppName : "default"; builder.RegisterType() .As() diff --git a/source/Server/Integration/Azure/AzureModule.cs b/source/Server/Integration/Azure/AzureModule.cs index 8a2220fcd..a981a41ce 100644 --- a/source/Server/Integration/Azure/AzureModule.cs +++ b/source/Server/Integration/Azure/AzureModule.cs @@ -36,7 +36,9 @@ private void RegisterCacheStore(ContainerBuilder builder) { builder .Register(c => { var connectionString = c.Resolve().GetSecret("PublicStorageConnectionString"); - return new BlobContainerClient(connectionString, "cache"); + var client = new BlobContainerClient(connectionString, "cache"); + client.CreateIfNotExists(); + return client; }) .Named(cacheClientName) .SingleInstance(); diff --git a/source/Server/Program.cs b/source/Server/Program.cs index 1196bc618..50e14414f 100644 --- a/source/Server/Program.cs +++ b/source/Server/Program.cs @@ -1,18 +1,26 @@ -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Autofac.Extensions.DependencyInjection; using SharpLab.Server.Common; +using Microsoft.AspNetCore.Builder; +using Autofac; namespace SharpLab.Server { public class Program { public static void Main(string[] args) { DotEnv.Load(); - CreateHostBuilder(args).Build().Run(); - } - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + var builder = WebApplication.CreateBuilder(args); + builder.AddServiceDefaults(); + builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); + + var startup = new Startup(); + startup.ConfigureServices(builder.Services); + builder.Host.ConfigureContainer(startup.ConfigureContainer); + + var app = builder.Build(); + app.MapDefaultEndpoints(); + startup.Configure(app, app.Environment); + app.Run(); + } } } diff --git a/source/Server/Server.csproj b/source/Server/Server.csproj index 0286d4676..49040364c 100644 --- a/source/Server/Server.csproj +++ b/source/Server/Server.csproj @@ -28,9 +28,7 @@ - - - + @@ -72,6 +70,7 @@ + diff --git a/source/ServiceDefaults/Extensions.cs b/source/ServiceDefaults/Extensions.cs new file mode 100644 index 000000000..db181d6f5 --- /dev/null +++ b/source/ServiceDefaults/Extensions.cs @@ -0,0 +1,129 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + Console.WriteLine("Adding service defaults..."); + builder.ConfigureOpenTelemetry(); + + Console.WriteLine("Adding default health checks..."); + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + Console.WriteLine("Configuring HTTP client defaults..."); + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + Console.WriteLine("Completing service defaults configuration..."); + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + // Exclude health check requests from tracing + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + .AddHttpClientInstrumentation(); + + tracing.AddSource(SharpLabActivities.Source.Name); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks(HealthEndpointPath); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/source/ServiceDefaults/ServiceDefaults.csproj b/source/ServiceDefaults/ServiceDefaults.csproj new file mode 100644 index 000000000..71f1baa7b --- /dev/null +++ b/source/ServiceDefaults/ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/source/ServiceDefaults/SharpLabActivities.cs b/source/ServiceDefaults/SharpLabActivities.cs new file mode 100644 index 000000000..b55a1b16c --- /dev/null +++ b/source/ServiceDefaults/SharpLabActivities.cs @@ -0,0 +1,7 @@ +using System.Diagnostics; + +namespace Microsoft.Extensions.Hosting; + +public static class SharpLabActivities { + public static ActivitySource Source { get; } = new ActivitySource("SharpLab", "1.0"); +} \ No newline at end of file diff --git a/source/SharpLab.sln b/source/SharpLab.sln index 54df06cac..753ecab9c 100644 --- a/source/SharpLab.sln +++ b/source/SharpLab.sln @@ -112,6 +112,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Internal.Roslyn411", "#exte EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Internal.Roslyn412", "#external\mirrorsharp-codemirror-6-preview\Internal.Roslyn412\Internal.Roslyn412.csproj", "{4CAF1FEC-CAC2-44BE-A72B-540877583C36}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "AppHost\AppHost.csproj", "{6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "ServiceDefaults\ServiceDefaults.csproj", "{FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -949,6 +953,42 @@ Global {4CAF1FEC-CAC2-44BE-A72B-540877583C36}.Release|x64.Build.0 = Release|Any CPU {4CAF1FEC-CAC2-44BE-A72B-540877583C36}.Release|x86.ActiveCfg = Release|Any CPU {4CAF1FEC-CAC2-44BE-A72B-540877583C36}.Release|x86.Build.0 = Release|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug|x64.ActiveCfg = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug|x64.Build.0 = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug|x86.Build.0 = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug-Vsix|Any CPU.ActiveCfg = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug-Vsix|Any CPU.Build.0 = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug-Vsix|x64.ActiveCfg = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug-Vsix|x64.Build.0 = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug-Vsix|x86.ActiveCfg = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Debug-Vsix|x86.Build.0 = Debug|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Release|Any CPU.Build.0 = Release|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Release|x64.ActiveCfg = Release|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Release|x64.Build.0 = Release|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Release|x86.ActiveCfg = Release|Any CPU + {6A7D4CCD-262D-4687-AAE7-B691E8E56ACA}.Release|x86.Build.0 = Release|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug|x64.ActiveCfg = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug|x64.Build.0 = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug|x86.ActiveCfg = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug|x86.Build.0 = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug-Vsix|Any CPU.ActiveCfg = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug-Vsix|Any CPU.Build.0 = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug-Vsix|x64.ActiveCfg = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug-Vsix|x64.Build.0 = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug-Vsix|x86.ActiveCfg = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Debug-Vsix|x86.Build.0 = Debug|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Release|Any CPU.Build.0 = Release|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Release|x64.ActiveCfg = Release|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Release|x64.Build.0 = Release|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Release|x86.ActiveCfg = Release|Any CPU + {FF1E7F3B-33E4-4064-8D2B-3B27D1B72F43}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/source/WebApp.Server/Program.cs b/source/WebApp.Server/Program.cs index 4b29aeb87..310bfde8d 100644 --- a/source/WebApp.Server/Program.cs +++ b/source/WebApp.Server/Program.cs @@ -1,5 +1,6 @@ +using Autofac; using Autofac.Extensions.DependencyInjection; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Hosting; using SharpLab.Server.Common; @@ -7,12 +8,19 @@ namespace SharpLab.WebApp.Server { public class Program { public static void Main(string[] args) { DotEnv.Load(); - CreateHostBuilder(args).Build().Run(); - } + + var builder = WebApplication.CreateBuilder(args); + builder.AddServiceDefaults(); + builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); + + var startup = new Startup(); + startup.ConfigureServices(builder.Services); + builder.Host.ConfigureContainer(startup.ConfigureContainer); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + var app = builder.Build(); + app.MapDefaultEndpoints(); + startup.Configure(app, app.Environment); + app.Run(); + } } } \ No newline at end of file diff --git a/source/WebApp.Server/WebApp.Server.csproj b/source/WebApp.Server/WebApp.Server.csproj index 3b3ff9124..b3681f03b 100644 --- a/source/WebApp.Server/WebApp.Server.csproj +++ b/source/WebApp.Server/WebApp.Server.csproj @@ -14,11 +14,10 @@ + - - diff --git a/source/WebApp/package.json b/source/WebApp/package.json index eaae987d6..edf19af19 100644 --- a/source/WebApp/package.json +++ b/source/WebApp/package.json @@ -9,6 +9,7 @@ "less": "ts-node-script ./scripts.ts less", "test": "jest", "start": "ts-node-script ./scripts.ts start --watch", + "start:fast": "ts-node-script ./scripts.ts start:fast", "build-ci": "ts-node-script ./scripts.ts build-ci", "storybook": "start-storybook -p 6006", "build-storybook": "ts-node-script ./scripts.ts storybook:build", diff --git a/source/WebApp/scripts.ts b/source/WebApp/scripts.ts index 7c682d503..6e8046381 100644 --- a/source/WebApp/scripts.ts +++ b/source/WebApp/scripts.ts @@ -9,6 +9,8 @@ import { manifest } from './scripts/manifest'; import { html, htmlOutputPath } from './scripts/html'; import './scripts/storybook'; +const port = process.env.PORT ?? '44200'; + const dirname = __dirname; const latest = task('latest', () => jetpack.writeAsync( @@ -37,9 +39,11 @@ const build = task('build', async () => { }); task('start', () => build(), { - watch: () => exec2('http-server', [outputSharedRoot, '-p', '44200', '--cors']) + watch: () => exec2('http-server', [outputSharedRoot, '-p', port, '--cors']) }); +task('start:fast', () => exec2('http-server', [outputSharedRoot, '-p', port, '--cors'])); + // Assumes we already ran the build const zip = task('zip', async () => { const AdmZip = (await import('adm-zip')).default; From 914805d79085aaf7e62cc1648f166ad6026174c6 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Mon, 2 Jun 2025 21:49:20 +0300 Subject: [PATCH 2/2] complete setup without running the setup.ps1 script --- .gitignore | 1 - source/AppHost/AppHost.cs | 57 +++++++++++++++++-- source/AppHost/AppHost.csproj | 1 + source/AppHost/Properties/launchSettings.json | 29 ++++++++++ .../Properties/launchSettings.json | 23 ++++++++ .../Properties/launchSettings.json | 23 ++++++++ 6 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 source/AppHost/Properties/launchSettings.json create mode 100644 source/Container.Manager/Properties/launchSettings.json create mode 100644 source/WebApp.Server/Properties/launchSettings.json diff --git a/.gitignore b/.gitignore index 25b19485b..f8bda13fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .env -launchSettings.json *.user *.pubxml diff --git a/source/AppHost/AppHost.cs b/source/AppHost/AppHost.cs index da257ff65..c6a473a98 100644 --- a/source/AppHost/AppHost.cs +++ b/source/AppHost/AppHost.cs @@ -1,18 +1,56 @@ -using System.Numerics; +using System.Management.Automation; +using Nivot.Aspire.Hosting.PowerShell; var builder = DistributedApplication.CreateBuilder(args); -var webappPort = builder.AddParameter("webapp-port", "44200"); +const string webappPort = "44200"; +const string ContainerHostAuthorizationToken = "d344827a-ca42-4159-95f6-5fb9551d62aa"; var storage = builder.AddAzureStorage("storage") .RunAsEmulator(options => options.WithLifetime(ContainerLifetime.Persistent).WithBlobPort(10000)); var storageBlob = storage.AddBlobs("cache"); var blobContainer = storageBlob.AddBlobContainer("edge"); -var containerManager = builder.AddProject("container-manager"); +var ps = builder.AddPowerShell("ps", PSLanguageMode.FullLanguage); -var installWebApp = builder.AddExecutable("install-webapp", "npm", "../WebApp", "install"); -var buildWebApp = builder.AddExecutable("build-webapp", "npm", "../WebApp", "run", "build") +var containerManagerBinPath = Path.Join(Directory.GetParent(new Projects.Container_Manager().ProjectPath)!.FullName, "bin", "Debug", "net9.0"); +var setupContainerManager = ps.AddScript("Preparing-container-host", +$""" +$containerCapabilityId = New-Object Security.Principal.SecurityIdentifier @( + 'S-1-15-3-1024-4233803318-1181731508-1220533431-3050556506-2713139869-1168708946-594703785-1824610955' +) +$aclRule = New-Object Security.AccessControl.FileSystemAccessRule @( + $containerCapabilityId, + [Security.AccessControl.FileSystemRights]::ReadAndExecute, + ([Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [Security.AccessControl.InheritanceFlags]::ObjectInherit), + [Security.AccessControl.PropagationFlags]::None, + [Security.AccessControl.AccessControlType]::Allow +) + +$binPath = '{containerManagerBinPath}' +$acl = Get-Acl $binPath +$acl.AddAccessRule($aclRule); +Set-Acl $binPath -AclObject $acl +""").WithArgs(containerManagerBinPath); + +var containerManager = builder.AddProject("container-manager") + .WithEnvironment("SHARPLAB_CONTAINER_HOST_AUTHORIZATION_TOKEN", ContainerHostAuthorizationToken) + .WaitForCompletion(setupContainerManager); + +var mirrorSharp = builder.AddExecutable("mirrorsharp-ci", "npm", "../#external/mirrorsharp/WebAssets", "ci"); +var mirrorSharpBuild = builder.AddNpmApp("mirrorsharp-build", "../#external/mirrorsharp/WebAssets", "build") + .WithParentRelationship(mirrorSharp) + .WaitForCompletion(mirrorSharp); + +var mirrorsharpPreview = builder.AddExecutable("mirrorsharp-preview", "npm", "../#external/mirrorsharp-codemirror-6-preview/WebAssets", "ci"); +var mirrorsharpPreviewBuild = builder.AddNpmApp("mirrorsharp-preview-build", "../#external/mirrorsharp-codemirror-6-preview/WebAssets", "build") + .WithParentRelationship(mirrorsharpPreview) + .WaitForCompletion(mirrorsharpPreview); + +var installWebApp = builder.AddExecutable("install-webapp", "npm", "../WebApp", "install") + .WaitForCompletion(mirrorSharpBuild) + .WaitForCompletion(mirrorsharpPreviewBuild); +var buildWebApp = builder.AddNpmApp("build-webapp", "../WebApp", "build") .WithParentRelationship(installWebApp) .WaitForCompletion(installWebApp); var webapp = builder.AddNpmApp("webapp", "../WebApp", scriptName: "start:fast") @@ -25,6 +63,13 @@ var webappServer = builder.AddProject("webapp-server") .WaitFor(webapp) .WaitFor(containerManager) - .WithEnvironment("SHARPLAB_LOCAL_SECRETS_PublicStorageConnectionString", storageBlob).WaitFor(storageBlob); + .WithEnvironment("SHARPLAB_LOCAL_SECRETS_PublicStorageConnectionString", storageBlob).WaitFor(storageBlob) + .WithEnvironment("SHARPLAB_CONTAINER_HOST_URL", containerManager.GetEndpoint("http")) + .WithEnvironment("SHARPLAB_ASSETS_BASE_URL", $"http://localhost:{webappPort}/") + .WithEnvironment("SHARPLAB_ASSETS_LATEST_URL_V2", $"http://localhost:{webappPort}/latest") + .WithEnvironment("SHARPLAB_LOCAL_SECRETS_ContainerHostAuthorizationToken", ContainerHostAuthorizationToken) + .WithEnvironment("SHARPLAB_WEBAPP_NAME", "local") + .WithEnvironment("SHARPLAB_CACHE_PATH_PREFIX", "edge") + .WithEnvironment("SHARPLAB_ASSETS_RELOAD_TOKEN", "12345"); builder.Build().Run(); diff --git a/source/AppHost/AppHost.csproj b/source/AppHost/AppHost.csproj index 9f568b467..35e602a4b 100644 --- a/source/AppHost/AppHost.csproj +++ b/source/AppHost/AppHost.csproj @@ -14,6 +14,7 @@ + diff --git a/source/AppHost/Properties/launchSettings.json b/source/AppHost/Properties/launchSettings.json new file mode 100644 index 000000000..684a34e33 --- /dev/null +++ b/source/AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17000;http://localhost:15000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21000", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22000" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19000", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20000" + } + } + } +} \ No newline at end of file diff --git a/source/Container.Manager/Properties/launchSettings.json b/source/Container.Manager/Properties/launchSettings.json new file mode 100644 index 000000000..5693163c7 --- /dev/null +++ b/source/Container.Manager/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5014;http://localhost:5015", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5015", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/source/WebApp.Server/Properties/launchSettings.json b/source/WebApp.Server/Properties/launchSettings.json new file mode 100644 index 000000000..e9b61f19c --- /dev/null +++ b/source/WebApp.Server/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:5013;http://localhost:5012", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5012", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file