diff --git a/Directory.Packages.props b/Directory.Packages.props
index 758fd10b..975cd9d1 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -11,6 +11,7 @@
+
diff --git a/ScriptBee.sln b/ScriptBee.sln
index 28dce80d..9c7e1564 100644
--- a/ScriptBee.sln
+++ b/ScriptBee.sln
@@ -53,6 +53,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.File", "src\Ada
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.File.Tests", "test\Adapters\Driven\Persistence.File.Tests\Persistence.File.Tests.csproj", "{86A8C10D-0701-4D44-BEAB-0982C0D733A2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ports.Plugins", "src\Application\Ports\Driven\Ports.Plugins\Ports.Plugins.csproj", "{FC474598-6399-46E2-8795-394E4F806052}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.InMemory", "src\Adapters\Driven\Persistence.InMemory\Persistence.InMemory.csproj", "{0426847C-4B33-434E-AC93-7C8C95AB5506}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.InMemory.Tests", "test\Adapters\Driven\Persistence.InMemory.Tests\Persistence.InMemory.Tests.csproj", "{348FE904-F636-4113-B63E-044B645F64C7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -163,5 +169,17 @@ Global
{86A8C10D-0701-4D44-BEAB-0982C0D733A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{86A8C10D-0701-4D44-BEAB-0982C0D733A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{86A8C10D-0701-4D44-BEAB-0982C0D733A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FC474598-6399-46E2-8795-394E4F806052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FC474598-6399-46E2-8795-394E4F806052}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FC474598-6399-46E2-8795-394E4F806052}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FC474598-6399-46E2-8795-394E4F806052}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0426847C-4B33-434E-AC93-7C8C95AB5506}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0426847C-4B33-434E-AC93-7C8C95AB5506}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0426847C-4B33-434E-AC93-7C8C95AB5506}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0426847C-4B33-434E-AC93-7C8C95AB5506}.Release|Any CPU.Build.0 = Release|Any CPU
+ {348FE904-F636-4113-B63E-044B645F64C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {348FE904-F636-4113-B63E-044B645F64C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {348FE904-F636-4113-B63E-044B645F64C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {348FE904-F636-4113-B63E-044B645F64C7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/ScriptBee/Models/Plugin.cs b/ScriptBee/Models/Plugin.cs
deleted file mode 100644
index 2fcf9fff..00000000
--- a/ScriptBee/Models/Plugin.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-using ScriptBee.Plugin.Manifest;
-
-namespace ScriptBee.Models;
-
-public record Plugin(
- string FolderPath,
- string Id,
- Version Version,
- PluginManifest Manifest
-);
diff --git a/ScriptBee/Plugin/IPluginRepository.cs b/ScriptBee/Plugin/IPluginRepository.cs
deleted file mode 100644
index 85063b4e..00000000
--- a/ScriptBee/Plugin/IPluginRepository.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using System.Collections.Generic;
-using DxWorks.ScriptBee.Plugin.Api;
-using ScriptBee.Plugin.Manifest;
-
-namespace ScriptBee.Plugin;
-
-public interface IPluginRepository
-{
- void UnRegisterPlugin(string pluginId, string pluginVersion);
-
- void RegisterPlugin(Models.Plugin plugin, Type @interface, Type concrete);
-
- void RegisterPlugin(Models.Plugin plugin);
-
- TService? GetPlugin(Func filter,
- IEnumerable<(Type @interface, object instance)>? services = null)
- where TService : IPlugin;
-
- IEnumerable GetPlugins(IEnumerable<(Type @interface, object instance)>? services = null)
- where TService : IPlugin;
-
- IEnumerable GetLoadedPluginsManifests();
-
- IEnumerable GetLoadedPlugins(string kind);
-
- IEnumerable GetLoadedPluginExtensionPoints()
- where T : PluginExtensionPoint;
-
- Version? GetInstalledPluginVersion(string pluginId);
-}
diff --git a/ScriptBee/Plugin/Manifest/LinkerPluginExtensionPoint.cs b/ScriptBee/Plugin/Manifest/LinkerPluginExtensionPoint.cs
deleted file mode 100644
index 01e58b95..00000000
--- a/ScriptBee/Plugin/Manifest/LinkerPluginExtensionPoint.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace ScriptBee.Plugin.Manifest;
-
-public class LinkerPluginExtensionPoint : PluginExtensionPoint
-{
-}
diff --git a/ScriptBee/Plugin/Manifest/LoaderPluginExtensionPoint.cs b/ScriptBee/Plugin/Manifest/LoaderPluginExtensionPoint.cs
deleted file mode 100644
index c8544343..00000000
--- a/ScriptBee/Plugin/Manifest/LoaderPluginExtensionPoint.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace ScriptBee.Plugin.Manifest;
-
-public class LoaderPluginExtensionPoint : PluginExtensionPoint
-{
-}
diff --git a/ScriptBee/Plugin/Manifest/PluginBundleExtensionPoint.cs b/ScriptBee/Plugin/Manifest/PluginBundleExtensionPoint.cs
deleted file mode 100644
index 628f75b0..00000000
--- a/ScriptBee/Plugin/Manifest/PluginBundleExtensionPoint.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace ScriptBee.Plugin.Manifest;
-
-public class PluginBundleExtensionPoint : PluginExtensionPoint
-{
-}
diff --git a/ScriptBee/Plugin/PluginRepository.cs b/ScriptBee/Plugin/PluginRepository.cs
deleted file mode 100644
index 8ab91932..00000000
--- a/ScriptBee/Plugin/PluginRepository.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using DxWorks.ScriptBee.Plugin.Api;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using ScriptBee.Plugin.Manifest;
-
-namespace ScriptBee.Plugin;
-
-// todo add tests
-public class PluginRepository : IPluginRepository
-{
- private readonly ConcurrentDictionary _plugins = new();
- private readonly ConcurrentBag _pluginServiceCollection = new();
-
- public void UnRegisterPlugin(string pluginId, string pluginVersion)
- {
- _plugins.Remove(pluginId, out _);
- }
-
- public void RegisterPlugin(Models.Plugin plugin, Type @interface, Type concrete)
- {
- RegisterPlugin(plugin);
-
- _pluginServiceCollection.Add(new ServiceDescriptor(@interface, concrete, ServiceLifetime.Singleton));
- }
-
- public void RegisterPlugin(Models.Plugin plugin)
- {
- _plugins.AddOrUpdate(plugin.Id, plugin, (_, manifest) => manifest.Version < plugin.Version ? plugin : manifest);
- }
-
- public TService? GetPlugin(Func filter,
- IEnumerable<(Type @interface, object instance)>? services = null) where TService : IPlugin
- {
- return GetPlugins().FirstOrDefault(filter);
- }
-
- public IEnumerable GetPlugins(IEnumerable<(Type @interface, object instance)>? services = null)
- where TService : IPlugin
- {
- var serviceCollection = new ServiceCollection
- {
- _pluginServiceCollection
- };
-
- if (services is not null)
- serviceCollection.Add(services.Select(s => new ServiceDescriptor(s.@interface, s.instance)));
-
- return serviceCollection.BuildServiceProvider()
- .GetServices();
- }
-
- public IEnumerable GetLoadedPluginsManifests()
- {
- return GetLoadedPlugins().Select(plugin => plugin.Manifest);
- }
-
- public IEnumerable GetLoadedPlugins(string kind)
- {
- return GetLoadedPlugins()
- .Where(plugin => plugin.Manifest.ExtensionPoints.Any(extensionPoint => extensionPoint.Kind == kind));
- }
-
- public IEnumerable GetLoadedPluginExtensionPoints() where T : PluginExtensionPoint
- {
- return GetLoadedPlugins().SelectMany(p => p.Manifest.ExtensionPoints).OfType();
- }
-
- public Version? GetInstalledPluginVersion(string pluginId)
- {
- return _plugins.TryGetValue(pluginId, out var plugin) ? plugin.Version : null;
- }
-
- private IEnumerable GetLoadedPlugins()
- {
- return _plugins.Values;
- }
-}
diff --git a/ScriptBeeWebApp/Services/GuidGenerator.cs b/ScriptBeeWebApp/Services/GuidGenerator.cs
deleted file mode 100644
index fef28364..00000000
--- a/ScriptBeeWebApp/Services/GuidGenerator.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using ScriptBee.Services;
-
-namespace ScriptBeeWebApp.Services;
-
-internal sealed class GuidGenerator : IGuidGenerator
-{
- public Guid GenerateGuid()
- {
- return Guid.NewGuid();
- }
-}
diff --git a/calculation.Dockerfile b/calculation.Dockerfile
index b7584fa9..a2e05771 100644
--- a/calculation.Dockerfile
+++ b/calculation.Dockerfile
@@ -15,10 +15,12 @@ COPY src/Application/Domain/Service.Analysis src/Application/Domain/Service.Anal
COPY src/Application/Ports/Driving/UseCases.Analysis src/Application/Ports/Driving/UseCases.Analysis
COPY src/Application/Ports/Driven/Ports.Analysis src/Application/Ports/Driven/Ports.Analysis
COPY src/Application/Ports/Driven/Ports.Files src/Application/Ports/Driven/Ports.Files
+COPY src/Application/Ports/Driven/Ports.Plugins src/Application/Ports/Driven/Ports.Plugins
COPY src/Application/Ports/Driven/Ports.Project src/Application/Ports/Driven/Ports.Project
-COPY src/Adapters/Driven/Persistence.Mongodb src/Adapters/Driven/Persistence.Mongodb
COPY src/Adapters/Driven/Persistence.File src/Adapters/Driven/Persistence.File
+COPY src/Adapters/Driven/Persistence.InMemory src/Adapters/Driven/Persistence.InMemory
+COPY src/Adapters/Driven/Persistence.Mongodb src/Adapters/Driven/Persistence.Mongodb
COPY src/Adapters/Driving/Common.Web src/Adapters/Driving/Common.Web
COPY src/Adapters/Driving/Calculation.Web src/Adapters/Driving/Calculation.Web
diff --git a/src/Adapters/Driven/Persistence.InMemory/Persistence.InMemory.csproj b/src/Adapters/Driven/Persistence.InMemory/Persistence.InMemory.csproj
new file mode 100644
index 00000000..33f95daf
--- /dev/null
+++ b/src/Adapters/Driven/Persistence.InMemory/Persistence.InMemory.csproj
@@ -0,0 +1,13 @@
+
+
+ ScriptBee.Persistence.InMemory
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Adapters/Driven/Persistence.InMemory/PluginRepository.cs b/src/Adapters/Driven/Persistence.InMemory/PluginRepository.cs
new file mode 100644
index 00000000..11a950bb
--- /dev/null
+++ b/src/Adapters/Driven/Persistence.InMemory/PluginRepository.cs
@@ -0,0 +1,91 @@
+using System.Collections.Concurrent;
+using DxWorks.ScriptBee.Plugin.Api;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using ScriptBee.Domain.Model.Plugin;
+using ScriptBee.Domain.Model.Plugin.Manifest;
+using ScriptBee.Ports.Plugins;
+
+namespace ScriptBee.Persistence.InMemory;
+
+public class PluginRepository : IPluginRepository
+{
+ private readonly ConcurrentDictionary _plugins = new();
+ private readonly ConcurrentBag _pluginServiceCollection = [];
+
+ public void UnRegisterPlugin(string pluginId, string pluginVersion)
+ {
+ _plugins.Remove(pluginId, out _);
+ }
+
+ public void RegisterPlugin(Plugin plugin, Type @interface, Type concrete)
+ {
+ RegisterPlugin(plugin);
+
+ _pluginServiceCollection.Add(
+ new ServiceDescriptor(@interface, concrete, ServiceLifetime.Singleton)
+ );
+ }
+
+ public void RegisterPlugin(Plugin plugin)
+ {
+ _plugins.AddOrUpdate(
+ plugin.Id,
+ plugin,
+ (_, manifest) => manifest.Version < plugin.Version ? plugin : manifest
+ );
+ }
+
+ public TService? GetPlugin(
+ Func filter,
+ IEnumerable<(Type @interface, object instance)>? services = null
+ )
+ where TService : IPlugin
+ {
+ return GetPlugins().FirstOrDefault(filter);
+ }
+
+ public IEnumerable GetPlugins(
+ IEnumerable<(Type @interface, object instance)>? services = null
+ )
+ where TService : IPlugin
+ {
+ var serviceCollection = new ServiceCollection { _pluginServiceCollection };
+
+ if (services is not null)
+ serviceCollection.Add(
+ services.Select(s => new ServiceDescriptor(s.@interface, s.instance))
+ );
+
+ return serviceCollection.BuildServiceProvider().GetServices();
+ }
+
+ public IEnumerable GetLoadedPluginsManifests()
+ {
+ return GetLoadedPlugins().Select(plugin => plugin.Manifest);
+ }
+
+ public IEnumerable GetLoadedPlugins(string kind)
+ {
+ return GetLoadedPlugins()
+ .Where(plugin =>
+ plugin.Manifest.ExtensionPoints.Any(extensionPoint => extensionPoint.Kind == kind)
+ );
+ }
+
+ public IEnumerable GetLoadedPluginExtensionPoints()
+ where T : PluginExtensionPoint
+ {
+ return GetLoadedPlugins().SelectMany(p => p.Manifest.ExtensionPoints).OfType();
+ }
+
+ public Version? GetInstalledPluginVersion(string pluginId)
+ {
+ return _plugins.TryGetValue(pluginId, out var plugin) ? plugin.Version : null;
+ }
+
+ private IEnumerable GetLoadedPlugins()
+ {
+ return _plugins.Values;
+ }
+}
diff --git a/src/Adapters/Driving/Analysis.Web/Analysis.Web.csproj b/src/Adapters/Driving/Analysis.Web/Analysis.Web.csproj
index cd75827e..b9c7e879 100644
--- a/src/Adapters/Driving/Analysis.Web/Analysis.Web.csproj
+++ b/src/Adapters/Driving/Analysis.Web/Analysis.Web.csproj
@@ -10,6 +10,7 @@
+
diff --git a/src/Adapters/Driving/Analysis.Web/Extensions/PluginsConfigExtensions.cs b/src/Adapters/Driving/Analysis.Web/Extensions/PluginsConfigExtensions.cs
new file mode 100644
index 00000000..a506a196
--- /dev/null
+++ b/src/Adapters/Driving/Analysis.Web/Extensions/PluginsConfigExtensions.cs
@@ -0,0 +1,12 @@
+using ScriptBee.Persistence.InMemory;
+using ScriptBee.Ports.Plugins;
+
+namespace ScriptBee.Analysis.Web.Extensions;
+
+public static class PluginsConfigExtensions
+{
+ public static IServiceCollection AddPluginsConfig(this IServiceCollection services)
+ {
+ return services.AddSingleton();
+ }
+}
diff --git a/src/Adapters/Driving/Analysis.Web/Program.cs b/src/Adapters/Driving/Analysis.Web/Program.cs
index f71efd69..87699d6f 100644
--- a/src/Adapters/Driving/Analysis.Web/Program.cs
+++ b/src/Adapters/Driving/Analysis.Web/Program.cs
@@ -20,7 +20,8 @@
.AddProblemDetailsDefaults()
.AddMongoDb(mongoConnectionString)
.AddCommonServices()
- .AddFileConfig(userFolderConfigurationSection);
+ .AddFileConfig(userFolderConfigurationSection)
+ .AddPluginsConfig();
builder.Services.AddEndpointDefinitions(
typeof(IEndpointDefinition),
diff --git a/ScriptBee/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs
similarity index 50%
rename from ScriptBee/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs
rename to src/Application/Domain/Model/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs
index f0fb1d73..437e6438 100644
--- a/ScriptBee/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs
+++ b/src/Application/Domain/Model/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs
@@ -1,5 +1,3 @@
-namespace ScriptBee.Plugin.Manifest;
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
-public class HelperFunctionsPluginExtensionPoint : PluginExtensionPoint
-{
-}
+public class HelperFunctionsPluginExtensionPoint : PluginExtensionPoint { }
diff --git a/src/Application/Domain/Model/Plugin/Manifest/LinkerPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/LinkerPluginExtensionPoint.cs
new file mode 100644
index 00000000..0756bba5
--- /dev/null
+++ b/src/Application/Domain/Model/Plugin/Manifest/LinkerPluginExtensionPoint.cs
@@ -0,0 +1,3 @@
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
+
+public class LinkerPluginExtensionPoint : PluginExtensionPoint { }
diff --git a/src/Application/Domain/Model/Plugin/Manifest/LoaderPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/LoaderPluginExtensionPoint.cs
new file mode 100644
index 00000000..246a74ae
--- /dev/null
+++ b/src/Application/Domain/Model/Plugin/Manifest/LoaderPluginExtensionPoint.cs
@@ -0,0 +1,3 @@
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
+
+public class LoaderPluginExtensionPoint : PluginExtensionPoint { }
diff --git a/src/Application/Domain/Model/Plugin/Manifest/PluginBundleExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/PluginBundleExtensionPoint.cs
new file mode 100644
index 00000000..517accf9
--- /dev/null
+++ b/src/Application/Domain/Model/Plugin/Manifest/PluginBundleExtensionPoint.cs
@@ -0,0 +1,3 @@
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
+
+public class PluginBundleExtensionPoint : PluginExtensionPoint { }
diff --git a/ScriptBee/Plugin/Manifest/PluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/PluginExtensionPoint.cs
similarity index 77%
rename from ScriptBee/Plugin/Manifest/PluginExtensionPoint.cs
rename to src/Application/Domain/Model/Plugin/Manifest/PluginExtensionPoint.cs
index 4b1123a2..83b91e3b 100644
--- a/ScriptBee/Plugin/Manifest/PluginExtensionPoint.cs
+++ b/src/Application/Domain/Model/Plugin/Manifest/PluginExtensionPoint.cs
@@ -1,4 +1,4 @@
-namespace ScriptBee.Plugin.Manifest;
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
public abstract class PluginExtensionPoint
{
diff --git a/ScriptBee/Plugin/Manifest/PluginKind.cs b/src/Application/Domain/Model/Plugin/Manifest/PluginKind.cs
similarity index 87%
rename from ScriptBee/Plugin/Manifest/PluginKind.cs
rename to src/Application/Domain/Model/Plugin/Manifest/PluginKind.cs
index 5005ddec..cea7c2c6 100644
--- a/ScriptBee/Plugin/Manifest/PluginKind.cs
+++ b/src/Application/Domain/Model/Plugin/Manifest/PluginKind.cs
@@ -1,4 +1,4 @@
-namespace ScriptBee.Plugin.Manifest;
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
public static class PluginKind
{
diff --git a/ScriptBee/Plugin/Manifest/PluginManifest.cs b/src/Application/Domain/Model/Plugin/Manifest/PluginManifest.cs
similarity index 80%
rename from ScriptBee/Plugin/Manifest/PluginManifest.cs
rename to src/Application/Domain/Model/Plugin/Manifest/PluginManifest.cs
index 5a09ebfa..6a3efea3 100644
--- a/ScriptBee/Plugin/Manifest/PluginManifest.cs
+++ b/src/Application/Domain/Model/Plugin/Manifest/PluginManifest.cs
@@ -1,6 +1,4 @@
-using System.Collections.Generic;
-
-namespace ScriptBee.Plugin.Manifest;
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
public class PluginManifest
{
diff --git a/ScriptBee/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs
similarity index 76%
rename from ScriptBee/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs
rename to src/Application/Domain/Model/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs
index 1db6a7dc..ac56fda0 100644
--- a/ScriptBee/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs
+++ b/src/Application/Domain/Model/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs
@@ -1,4 +1,4 @@
-namespace ScriptBee.Plugin.Manifest;
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
public class ScriptGeneratorPluginExtensionPoint : PluginExtensionPoint
{
diff --git a/ScriptBee/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs
similarity index 69%
rename from ScriptBee/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs
rename to src/Application/Domain/Model/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs
index 282db994..5a0dc0df 100644
--- a/ScriptBee/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs
+++ b/src/Application/Domain/Model/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs
@@ -1,4 +1,4 @@
-namespace ScriptBee.Plugin.Manifest;
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
public class ScriptRunnerPluginExtensionPoint : PluginExtensionPoint
{
diff --git a/ScriptBee/Plugin/Manifest/UiPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/UiPluginExtensionPoint.cs
similarity index 85%
rename from ScriptBee/Plugin/Manifest/UiPluginExtensionPoint.cs
rename to src/Application/Domain/Model/Plugin/Manifest/UiPluginExtensionPoint.cs
index aaa0fdcd..ddf6f9be 100644
--- a/ScriptBee/Plugin/Manifest/UiPluginExtensionPoint.cs
+++ b/src/Application/Domain/Model/Plugin/Manifest/UiPluginExtensionPoint.cs
@@ -1,4 +1,4 @@
-namespace ScriptBee.Plugin.Manifest;
+namespace ScriptBee.Domain.Model.Plugin.Manifest;
public class UiPluginExtensionPoint : PluginExtensionPoint
{
diff --git a/src/Application/Domain/Model/Plugin/Plugin.cs b/src/Application/Domain/Model/Plugin/Plugin.cs
new file mode 100644
index 00000000..13fb48cc
--- /dev/null
+++ b/src/Application/Domain/Model/Plugin/Plugin.cs
@@ -0,0 +1,5 @@
+using ScriptBee.Domain.Model.Plugin.Manifest;
+
+namespace ScriptBee.Domain.Model.Plugin;
+
+public record Plugin(string FolderPath, string Id, Version Version, PluginManifest Manifest);
diff --git a/src/Application/Domain/Model/Plugin/ScriptRunnerNotFoundError.cs b/src/Application/Domain/Model/Plugin/ScriptRunnerNotFoundError.cs
new file mode 100644
index 00000000..cb4e2782
--- /dev/null
+++ b/src/Application/Domain/Model/Plugin/ScriptRunnerNotFoundError.cs
@@ -0,0 +1,11 @@
+using ScriptBee.Domain.Model.ProjectStructure;
+
+namespace ScriptBee.Domain.Model.Plugin;
+
+public record ScriptRunnerNotFoundError(ScriptLanguage Language)
+{
+ public override string ToString()
+ {
+ return $"Runner for language '{Language.Name}' does not exist.";
+ }
+}
diff --git a/src/Application/Domain/Service.Analysis/RunAnalysisService.cs b/src/Application/Domain/Service.Analysis/RunAnalysisService.cs
index 2d441b94..60a5cfe4 100644
--- a/src/Application/Domain/Service.Analysis/RunAnalysisService.cs
+++ b/src/Application/Domain/Service.Analysis/RunAnalysisService.cs
@@ -1,8 +1,12 @@
-using ScriptBee.Common;
+using DxWorks.ScriptBee.Plugin.Api;
+using OneOf;
+using ScriptBee.Common;
using ScriptBee.Domain.Model.Analysis;
+using ScriptBee.Domain.Model.Plugin;
using ScriptBee.Domain.Model.ProjectStructure;
using ScriptBee.Ports.Analysis;
using ScriptBee.Ports.Files;
+using ScriptBee.Ports.Plugins;
using ScriptBee.Ports.Project.Structure;
using ScriptBee.UseCases.Analysis;
@@ -13,7 +17,8 @@ public class RunAnalysisService(
IGuidProvider guidProvider,
ICreateAnalysis createAnalysis,
IGetScript getScript,
- ILoadFile loadFile
+ ILoadFile loadFile,
+ IPluginRepository pluginRepository
) : IRunAnalysisUseCase
{
public async Task Run(
@@ -43,6 +48,30 @@ private async Task Run(
Script script,
CancellationToken cancellationToken = default
)
+ {
+ var scriptRunnerResult = GetScriptRunner(script.ScriptLanguage);
+
+ return await scriptRunnerResult.Match(
+ runner => Run(runner, script, cancellationToken),
+ async error =>
+ await createAnalysis.Create(
+ AnalysisInfo.FailedToStart(
+ new AnalysisId(guidProvider.NewGuid()),
+ script.ProjectId,
+ script.Id,
+ dateTimeProvider.UtcNow(),
+ error.ToString()
+ ),
+ cancellationToken
+ )
+ );
+ }
+
+ private async Task Run(
+ IScriptRunner scriptRunner,
+ Script script,
+ CancellationToken cancellationToken = default
+ )
{
var analysisInfo = await createAnalysis.Create(
AnalysisInfo.Started(
@@ -59,4 +88,17 @@ private async Task Run(
return analysisInfo;
}
+
+ private OneOf GetScriptRunner(
+ ScriptLanguage scriptLanguage
+ )
+ {
+ var scriptRunner = pluginRepository.GetPlugin(runner =>
+ runner.Language == scriptLanguage.Name
+ );
+
+ return scriptRunner is null
+ ? new ScriptRunnerNotFoundError(scriptLanguage)
+ : OneOf.FromT0(scriptRunner);
+ }
}
diff --git a/src/Application/Domain/Service.Analysis/Service.Analysis.csproj b/src/Application/Domain/Service.Analysis/Service.Analysis.csproj
index abe2669e..52f8af47 100644
--- a/src/Application/Domain/Service.Analysis/Service.Analysis.csproj
+++ b/src/Application/Domain/Service.Analysis/Service.Analysis.csproj
@@ -7,6 +7,7 @@
+
diff --git a/src/Application/Ports/Driven/Ports.Plugins/IPluginRepository.cs b/src/Application/Ports/Driven/Ports.Plugins/IPluginRepository.cs
new file mode 100644
index 00000000..6e143d13
--- /dev/null
+++ b/src/Application/Ports/Driven/Ports.Plugins/IPluginRepository.cs
@@ -0,0 +1,34 @@
+using DxWorks.ScriptBee.Plugin.Api;
+using ScriptBee.Domain.Model.Plugin;
+using ScriptBee.Domain.Model.Plugin.Manifest;
+
+namespace ScriptBee.Ports.Plugins;
+
+public interface IPluginRepository
+{
+ void UnRegisterPlugin(string pluginId, string pluginVersion);
+
+ void RegisterPlugin(Plugin plugin, Type @interface, Type concrete);
+
+ void RegisterPlugin(Plugin plugin);
+
+ TService? GetPlugin(
+ Func filter,
+ IEnumerable<(Type @interface, object instance)>? services = null
+ )
+ where TService : IPlugin;
+
+ IEnumerable GetPlugins(
+ IEnumerable<(Type @interface, object instance)>? services = null
+ )
+ where TService : IPlugin;
+
+ IEnumerable GetLoadedPluginsManifests();
+
+ IEnumerable GetLoadedPlugins(string kind);
+
+ IEnumerable GetLoadedPluginExtensionPoints()
+ where T : PluginExtensionPoint;
+
+ Version? GetInstalledPluginVersion(string pluginId);
+}
diff --git a/src/Application/Ports/Driven/Ports.Plugins/Ports.Plugins.csproj b/src/Application/Ports/Driven/Ports.Plugins/Ports.Plugins.csproj
new file mode 100644
index 00000000..7565f998
--- /dev/null
+++ b/src/Application/Ports/Driven/Ports.Plugins/Ports.Plugins.csproj
@@ -0,0 +1,9 @@
+
+
+ ScriptBee.Ports.Plugins
+
+
+
+
+
+
diff --git a/test/Adapters/Driven/Persistence.InMemory.Tests/Persistence.InMemory.Tests.csproj b/test/Adapters/Driven/Persistence.InMemory.Tests/Persistence.InMemory.Tests.csproj
new file mode 100644
index 00000000..86b16b40
--- /dev/null
+++ b/test/Adapters/Driven/Persistence.InMemory.Tests/Persistence.InMemory.Tests.csproj
@@ -0,0 +1,9 @@
+
+
+ ScriptBee.Persistence.InMemory.Tests
+
+
+
+
+
+
diff --git a/test/Adapters/Driven/Persistence.InMemory.Tests/PluginRepositoryTest.cs b/test/Adapters/Driven/Persistence.InMemory.Tests/PluginRepositoryTest.cs
new file mode 100644
index 00000000..57614ea1
--- /dev/null
+++ b/test/Adapters/Driven/Persistence.InMemory.Tests/PluginRepositoryTest.cs
@@ -0,0 +1,214 @@
+using DxWorks.ScriptBee.Plugin.Api;
+using ScriptBee.Domain.Model.Plugin;
+using ScriptBee.Domain.Model.Plugin.Manifest;
+
+namespace ScriptBee.Persistence.InMemory.Tests;
+
+public class PluginRepositoryTests
+{
+ private readonly PluginRepository _repository = new();
+
+ [Fact]
+ public void RegisterPlugin_AddsPluginToRepository()
+ {
+ var plugin = CreateTestPlugin("testId", new Version(1, 0, 0));
+
+ _repository.RegisterPlugin(plugin);
+
+ Assert.Single(_repository.GetLoadedPluginsManifests());
+ Assert.Equal("testId", _repository.GetLoadedPluginsManifests().First().Name);
+ }
+
+ [Fact]
+ public void RegisterPlugin_ReplacesOlderVersion()
+ {
+ var plugin1 = CreateTestPlugin("testId", new Version(1, 0, 0));
+ var plugin2 = CreateTestPlugin("testId", new Version(2, 0, 0));
+
+ _repository.RegisterPlugin(plugin1);
+ _repository.RegisterPlugin(plugin2);
+
+ Assert.Single(_repository.GetLoadedPluginsManifests());
+ Assert.Equal(new Version(2, 0, 0), _repository.GetInstalledPluginVersion("testId"));
+ }
+
+ [Fact]
+ public void RegisterPlugin_DoesNotReplaceNewerVersion()
+ {
+ var plugin1 = CreateTestPlugin("testId", new Version(2, 0, 0));
+ var plugin2 = CreateTestPlugin("testId", new Version(1, 0, 0));
+
+ _repository.RegisterPlugin(plugin1);
+ _repository.RegisterPlugin(plugin2);
+
+ Assert.Single(_repository.GetLoadedPluginsManifests());
+ Assert.Equal(new Version(2, 0, 0), _repository.GetInstalledPluginVersion("testId"));
+ }
+
+ [Fact]
+ public void UnRegisterPlugin_RemovesPluginFromRepository()
+ {
+ var plugin = CreateTestPlugin("testId", new Version(1, 0, 0));
+ _repository.RegisterPlugin(plugin);
+
+ _repository.UnRegisterPlugin("testId", "1.0.0");
+
+ Assert.Empty(_repository.GetLoadedPluginsManifests());
+ }
+
+ [Fact]
+ public void RegisterPlugin_WithInterfaceAndConcrete_AddsServiceDescriptor()
+ {
+ var plugin = CreateTestPlugin("testId", new Version(1, 0, 0));
+
+ _repository.RegisterPlugin(plugin, typeof(ITestPlugin), typeof(TestPlugin));
+
+ var services = _repository.GetPlugins();
+ Assert.Single(services);
+ }
+
+ [Fact]
+ public void GetPlugin_ReturnsPluginMatchingFilter()
+ {
+ var plugin1 = CreateTestPlugin("testId1", new Version(1, 0, 0));
+ var plugin2 = CreateTestPlugin("testId2", new Version(1, 0, 0));
+ _repository.RegisterPlugin(plugin1, typeof(ITestPlugin), typeof(TestPlugin));
+ _repository.RegisterPlugin(plugin2, typeof(ITestPlugin), typeof(TestPlugin2));
+
+ var result = _repository.GetPlugin(p => p.GetPluginId() == "testId2");
+
+ Assert.NotNull(result);
+ Assert.Equal("testId2", result.GetPluginId());
+ }
+
+ [Fact]
+ public void GetPlugins_ReturnsAllPluginsOfGivenType()
+ {
+ var plugin1 = CreateTestPlugin("testId1", new Version(1, 0, 0));
+ var plugin2 = CreateTestPlugin("testId2", new Version(1, 0, 0));
+ _repository.RegisterPlugin(plugin1, typeof(ITestPlugin), typeof(TestPlugin));
+ _repository.RegisterPlugin(plugin2, typeof(ITestPlugin), typeof(TestPlugin2));
+
+ var result = _repository.GetPlugins();
+
+ Assert.Equal(2, result.Count());
+ }
+
+ [Fact]
+ public void GetLoadedPluginsManifests_ReturnsManifestsOfLoadedPlugins()
+ {
+ var plugin1 = CreateTestPlugin("testId1", new Version(1, 0, 0));
+ var plugin2 = CreateTestPlugin("testId2", new Version(1, 0, 0));
+ _repository.RegisterPlugin(plugin1);
+ _repository.RegisterPlugin(plugin2);
+
+ var manifests = _repository.GetLoadedPluginsManifests().ToList();
+
+ Assert.Equal(2, manifests.Count);
+ Assert.Contains(manifests, m => m.Name == "testId1");
+ Assert.Contains(manifests, m => m.Name == "testId2");
+ }
+
+ [Fact]
+ public void GetLoadedPlugins_WithKind_ReturnsPluginsWithMatchingKind()
+ {
+ var plugin1 = CreateTestPlugin(
+ "testId1",
+ new Version(1, 0, 0),
+ new TestExtensionPoint { Kind = "testKind" }
+ );
+ var plugin2 = CreateTestPlugin(
+ "testId2",
+ new Version(1, 0, 0),
+ new TestExtensionPoint { Kind = "anotherKind" }
+ );
+ _repository.RegisterPlugin(plugin1);
+ _repository.RegisterPlugin(plugin2);
+
+ var plugins = _repository.GetLoadedPlugins("testKind").ToList();
+
+ Assert.Single(plugins);
+ Assert.Equal("testId1", plugins.First().Id);
+ }
+
+ [Fact]
+ public void GetLoadedPluginExtensionPoints_ReturnsExtensionPointsOfGivenType()
+ {
+ var plugin1 = CreateTestPlugin(
+ "testId1",
+ new Version(1, 0, 0),
+ new TestExtensionPoint { Kind = "testKind" }
+ );
+ var plugin2 = CreateTestPlugin(
+ "testId2",
+ new Version(1, 0, 0),
+ new AnotherTestExtensionPoint { Kind = "anotherKind" }
+ );
+ _repository.RegisterPlugin(plugin1);
+ _repository.RegisterPlugin(plugin2);
+
+ var extensionPoints = _repository
+ .GetLoadedPluginExtensionPoints()
+ .ToList();
+
+ Assert.Single(extensionPoints);
+ Assert.Equal("testKind", extensionPoints.First().Kind);
+ }
+
+ [Fact]
+ public void GetInstalledPluginVersion_ReturnsInstalledPluginVersion()
+ {
+ var plugin = CreateTestPlugin("testId", new Version(1, 0, 0));
+ _repository.RegisterPlugin(plugin);
+
+ var version = _repository.GetInstalledPluginVersion("testId");
+ Assert.Equal(new Version(1, 0, 0), version);
+ }
+
+ [Fact]
+ public void GetInstalledPluginVersion_ReturnsNullIfPluginNotInstalled()
+ {
+ var version = _repository.GetInstalledPluginVersion("nonExistentId");
+
+ Assert.Null(version);
+ }
+
+ private static Plugin CreateTestPlugin(
+ string id,
+ Version version,
+ params PluginExtensionPoint[] extensionPoints
+ )
+ {
+ return new Plugin(
+ "test/path",
+ id,
+ version,
+ new PluginManifest { Name = id, ExtensionPoints = extensionPoints.ToList() }
+ );
+ }
+}
+
+file class TestExtensionPoint : PluginExtensionPoint;
+
+file class AnotherTestExtensionPoint : PluginExtensionPoint;
+
+file interface ITestPlugin : IPlugin
+{
+ string GetPluginId();
+}
+
+file class TestPlugin : ITestPlugin
+{
+ public string GetPluginId()
+ {
+ return "testId1";
+ }
+}
+
+file class TestPlugin2 : ITestPlugin
+{
+ public string GetPluginId()
+ {
+ return "testId2";
+ }
+}
diff --git a/test/Application/Domain/Service.Analysis.Tests/RunAnalysisServiceTest.cs b/test/Application/Domain/Service.Analysis.Tests/RunAnalysisServiceTest.cs
index a1c0cd3a..dd3a5b35 100644
--- a/test/Application/Domain/Service.Analysis.Tests/RunAnalysisServiceTest.cs
+++ b/test/Application/Domain/Service.Analysis.Tests/RunAnalysisServiceTest.cs
@@ -1,4 +1,5 @@
-using NSubstitute;
+using DxWorks.ScriptBee.Plugin.Api;
+using NSubstitute;
using OneOf;
using ScriptBee.Common;
using ScriptBee.Domain.Model.Analysis;
@@ -6,6 +7,7 @@
using ScriptBee.Domain.Model.ProjectStructure;
using ScriptBee.Ports.Analysis;
using ScriptBee.Ports.Files;
+using ScriptBee.Ports.Plugins;
using ScriptBee.Ports.Project.Structure;
using ScriptBee.Service.Analysis;
using ScriptBee.UseCases.Analysis;
@@ -19,6 +21,9 @@ public class RunAnalysisServiceTest
private readonly ICreateAnalysis _createAnalysis = Substitute.For();
private readonly IGetScript _getScript = Substitute.For();
private readonly ILoadFile _loadFile = Substitute.For();
+ private readonly IPluginRepository _pluginRepository = Substitute.For();
+
+ private readonly IScriptRunner _scriptRunner = Substitute.For();
private readonly RunAnalysisService _runAnalysisService;
@@ -29,7 +34,8 @@ public RunAnalysisServiceTest()
_guidProvider,
_createAnalysis,
_getScript,
- _loadFile
+ _loadFile,
+ _pluginRepository
);
}
@@ -74,6 +80,7 @@ public async Task CreateAnalysisSuccessful()
Arg.Any()
)
.Returns(expectedAnalysisInfo);
+ _pluginRepository.GetPlugin(Arg.Any>()).Returns(_scriptRunner);
var analysisResult = await _runAnalysisService.Run(command);
@@ -83,6 +90,56 @@ await _loadFile
.GetScriptContent(projectId, "path", Arg.Any());
}
+ [Fact]
+ public async Task GivenScriptRunnerDoesNotExistsError_thenAnalysisIsFailed()
+ {
+ var date = DateTimeOffset.UtcNow;
+ var projectId = ProjectId.FromValue("project-id");
+ var analysisId = new AnalysisId(Guid.Parse("97c3498a-1a8f-4e01-be79-0cc9ede9f7a8"));
+ var scriptId = new ScriptId(Guid.Parse("38900b32-fca2-4213-bd4b-c71cc1068bb6"));
+ var command = new RunAnalysisCommand(projectId, scriptId);
+ var expectedAnalysisInfo = new AnalysisInfo(
+ analysisId,
+ projectId,
+ scriptId,
+ AnalysisStatus.Finished,
+ [],
+ [new AnalysisError("Runner for language 'language' does not exist.")],
+ date,
+ date
+ );
+ _dateTimeProvider.UtcNow().Returns(date);
+ _guidProvider.NewGuid().Returns(analysisId.Value);
+ _getScript
+ .Get(scriptId, Arg.Any())
+ .Returns(
+ Task.FromResult>(
+ new Script(
+ scriptId,
+ projectId,
+ "script",
+ "path",
+ "absolute-path",
+ new ScriptLanguage("language", ".lang"),
+ []
+ )
+ )
+ );
+ _createAnalysis
+ .Create(
+ Arg.Is(info => info.MatchAnalysisResult(expectedAnalysisInfo)),
+ Arg.Any()
+ )
+ .Returns(expectedAnalysisInfo);
+ _pluginRepository
+ .GetPlugin(Arg.Any>())
+ .Returns((IScriptRunner?)null);
+
+ var analysisResult = await _runAnalysisService.Run(command);
+
+ analysisResult.AssertAnalysisResult(expectedAnalysisInfo);
+ }
+
[Fact]
public async Task GivenScriptDoesNotExistsError_thenAnalysisIsFailed()
{