diff --git a/src/SquidStd.Plugin/Extensions/SquidStdBootstrapPluginExtensions.cs b/src/SquidStd.Plugin/Extensions/SquidStdBootstrapPluginExtensions.cs index 9de5732..e9fb85f 100644 --- a/src/SquidStd.Plugin/Extensions/SquidStdBootstrapPluginExtensions.cs +++ b/src/SquidStd.Plugin/Extensions/SquidStdBootstrapPluginExtensions.cs @@ -18,7 +18,8 @@ public static class SquidStdBootstrapPluginExtensions /// across the whole set, and invokes for each plugin /// in order against the bootstrap container. Must be called before the bootstrap starts, so /// plugins can register configuration sections before the configuration is loaded. Relative - /// directories are resolved against the bootstrap root directory. Any failure aborts startup: + /// directories are resolved against the bootstrap root directory and created when missing + /// (an empty directory yields no plugins). Any failure aborts startup: /// loader problems raise and plugin exceptions /// propagate unchanged. Plugin assemblies load into the default AssemblyLoadContext and are /// fully trusted: there is no unloading and no version isolation. Note that plugin load @@ -56,6 +57,10 @@ Action configure ? directory : Path.Combine(bootstrap.Options.RootDirectory, directory); + // Managed like the bootstrap's DirectoriesConfig entries: a missing plugin + // directory is created (and simply yields no plugins), not treated as an error. + Directory.CreateDirectory(resolved); + plugins.AddRange(PluginAssemblyScanner.Scan(resolved)); } diff --git a/src/SquidStd.Plugin/PluginCollectionBuilder.cs b/src/SquidStd.Plugin/PluginCollectionBuilder.cs index 324601c..79d22ea 100644 --- a/src/SquidStd.Plugin/PluginCollectionBuilder.cs +++ b/src/SquidStd.Plugin/PluginCollectionBuilder.cs @@ -44,7 +44,8 @@ public PluginCollectionBuilder Add(ISquidStdPlugin plugin) /// /// Adds a directory to scan for external plugin assemblies (*.dll, non-recursive). - /// Relative paths are resolved against the bootstrap root directory. + /// Relative paths are resolved against the bootstrap root directory; a missing + /// directory is created and yields no plugins. /// /// Absolute or root-relative directory path. /// The same builder for chaining. diff --git a/src/SquidStd.Plugin/README.md b/src/SquidStd.Plugin/README.md index ec54bac..53e812d 100644 --- a/src/SquidStd.Plugin/README.md +++ b/src/SquidStd.Plugin/README.md @@ -40,6 +40,8 @@ await bootstrap.RunAsync(); can depend on an internal one and vice versa. - `Configure` runs before the configuration load, so plugins can register their own configuration sections and services ahead of time. +- Plugin directories are managed like the other bootstrap directories: a missing directory is + created on the spot and simply yields no plugins. - Each plugin receives a `PluginContext` populated with the standard keys: `PluginContextKeys.RootDirectory` (the bootstrap root directory) and `PluginContextKeys.AppName` (the bootstrap app name). diff --git a/tests/SquidStd.Tests/Plugin/SquidStdBootstrapPluginExtensionsTests.cs b/tests/SquidStd.Tests/Plugin/SquidStdBootstrapPluginExtensionsTests.cs index f9e159f..55e38a8 100644 --- a/tests/SquidStd.Tests/Plugin/SquidStdBootstrapPluginExtensionsTests.cs +++ b/tests/SquidStd.Tests/Plugin/SquidStdBootstrapPluginExtensionsTests.cs @@ -93,6 +93,20 @@ public async Task UsePlugins_LoadsExternalPluginsFromRelativeDirectory() Assert.Equal("tests.external", bootstrap.Container.Resolve(serviceKey: "plugin-marker")); } + [Fact] + public async Task UsePlugins_MissingDirectory_IsCreatedAndYieldsNoPlugins() + { + using var root = new TempDirectory(); + + await using var bootstrap = SquidStdBootstrap.Create( + new SquidStdOptions { ConfigName = "plugintest", RootDirectory = root.Path } + ); + + bootstrap.UsePlugins(plugins => plugins.FromDirectory("plugins")); + + Assert.True(Directory.Exists(root.Combine("plugins"))); + } + [Fact] public async Task UsePlugins_AfterStart_Throws() {