From 5aef99bbdecd4eecdd878f1bfbe24fc990d3b796 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:32:31 +0000 Subject: [PATCH 1/2] Initial plan From 189b004095db520ba9f0ad952c3ae540e1cd2978 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:40:51 +0000 Subject: [PATCH 2/2] Add plugins/all package for simplified built-in plugin registration Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> --- cmd/server/main.go | 53 +---------------- example/go.mod | 5 ++ example/go.sum | 2 + example/main.go | 14 ++--- plugins/all/all.go | 101 ++++++++++++++++++++++++++++++++ plugins/all/all_test.go | 124 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 241 insertions(+), 58 deletions(-) create mode 100644 plugins/all/all.go create mode 100644 plugins/all/all_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 6b383b01..95e4120c 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -40,31 +40,8 @@ import ( _ "github.com/GoCodeAlone/workflow/plugin/docmanager" pluginexternal "github.com/GoCodeAlone/workflow/plugin/external" _ "github.com/GoCodeAlone/workflow/plugin/storebrowser" - pluginai "github.com/GoCodeAlone/workflow/plugins/ai" - pluginapi "github.com/GoCodeAlone/workflow/plugins/api" - pluginauth "github.com/GoCodeAlone/workflow/plugins/auth" - plugincicd "github.com/GoCodeAlone/workflow/plugins/cicd" - plugindlq "github.com/GoCodeAlone/workflow/plugins/dlq" - pluginevstore "github.com/GoCodeAlone/workflow/plugins/eventstore" - pluginff "github.com/GoCodeAlone/workflow/plugins/featureflags" - pluginhttp "github.com/GoCodeAlone/workflow/plugins/http" - pluginintegration "github.com/GoCodeAlone/workflow/plugins/integration" - pluginlicense "github.com/GoCodeAlone/workflow/plugins/license" - pluginmessaging "github.com/GoCodeAlone/workflow/plugins/messaging" - pluginmodcompat "github.com/GoCodeAlone/workflow/plugins/modularcompat" - pluginobs "github.com/GoCodeAlone/workflow/plugins/observability" + allplugins "github.com/GoCodeAlone/workflow/plugins/all" pluginpipeline "github.com/GoCodeAlone/workflow/plugins/pipelinesteps" - plugincloud "github.com/GoCodeAlone/workflow/plugins/cloud" - plugindatastores "github.com/GoCodeAlone/workflow/plugins/datastores" - plugingitlab "github.com/GoCodeAlone/workflow/plugins/gitlab" - pluginmarketplace "github.com/GoCodeAlone/workflow/plugins/marketplace" - pluginplatform "github.com/GoCodeAlone/workflow/plugins/platform" - pluginpolicy "github.com/GoCodeAlone/workflow/plugins/policy" - pluginscheduler "github.com/GoCodeAlone/workflow/plugins/scheduler" - pluginsecrets "github.com/GoCodeAlone/workflow/plugins/secrets" - pluginsm "github.com/GoCodeAlone/workflow/plugins/statemachine" - pluginstorage "github.com/GoCodeAlone/workflow/plugins/storage" - plugintimeline "github.com/GoCodeAlone/workflow/plugins/timeline" "github.com/GoCodeAlone/workflow/provider" _ "github.com/GoCodeAlone/workflow/provider/aws" _ "github.com/GoCodeAlone/workflow/provider/azure" @@ -109,33 +86,7 @@ var ( // defaultEnginePlugins returns the standard set of engine plugins used by all engine instances. // Centralising the list here avoids duplication between buildEngine and runMultiWorkflow. func defaultEnginePlugins() []plugin.EnginePlugin { - return []plugin.EnginePlugin{ - pluginlicense.New(), - pluginhttp.New(), - pluginobs.New(), - pluginmessaging.New(), - pluginsm.New(), - pluginauth.New(), - pluginstorage.New(), - pluginapi.New(), - pluginpipeline.New(), - plugincicd.New(), - pluginff.New(), - pluginevstore.New(), - plugintimeline.New(), - plugindlq.New(), - pluginsecrets.New(), - pluginmodcompat.New(), - pluginscheduler.New(), - pluginintegration.New(), - pluginai.New(), - pluginplatform.New(), - plugincloud.New(), - plugingitlab.New(), - plugindatastores.New(), - pluginpolicy.New(), - pluginmarketplace.New(), - } + return allplugins.DefaultPlugins() } // buildEngine creates the workflow engine with all handlers registered and built from config. diff --git a/example/go.mod b/example/go.mod index 1aaadf7b..072feed5 100644 --- a/example/go.mod +++ b/example/go.mod @@ -21,7 +21,10 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/CrisisTextLine/modular/modules/auth v0.4.0 // indirect + github.com/CrisisTextLine/modular/modules/cache v0.4.0 // indirect github.com/CrisisTextLine/modular/modules/eventbus/v2 v2.0.0 // indirect + github.com/CrisisTextLine/modular/modules/reverseproxy/v2 v2.2.0 // indirect + github.com/CrisisTextLine/modular/modules/scheduler v0.4.0 // indirect github.com/DataDog/datadog-go/v5 v5.4.0 // indirect github.com/GoCodeAlone/yaegi v0.17.1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect @@ -82,6 +85,7 @@ require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/golobby/cast v1.3.3 // indirect @@ -138,6 +142,7 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/redis/go-redis/v9 v9.18.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect diff --git a/example/go.sum b/example/go.sum index 4ada9598..16d6c141 100644 --- a/example/go.sum +++ b/example/go.sum @@ -184,6 +184,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= +github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= diff --git a/example/main.go b/example/main.go index a341a4a7..29cb6c59 100644 --- a/example/main.go +++ b/example/main.go @@ -17,7 +17,7 @@ import ( "github.com/CrisisTextLine/modular" "github.com/GoCodeAlone/workflow" "github.com/GoCodeAlone/workflow/config" - "github.com/GoCodeAlone/workflow/handlers" + allplugins "github.com/GoCodeAlone/workflow/plugins/all" ) func main() { @@ -50,12 +50,12 @@ func main() { // Create workflow engine engine := workflow.NewStdEngine(app, logger) - // Register workflow handlers - engine.RegisterWorkflowHandler(handlers.NewHTTPWorkflowHandler()) - engine.RegisterWorkflowHandler(handlers.NewMessagingWorkflowHandler()) - engine.RegisterWorkflowHandler(handlers.NewStateMachineWorkflowHandler()) - engine.RegisterWorkflowHandler(handlers.NewSchedulerWorkflowHandler()) - engine.RegisterWorkflowHandler(handlers.NewIntegrationWorkflowHandler()) + // Load all built-in plugins in one call. + // To use a custom set, call allplugins.DefaultPlugins(), modify the slice, + // and load each plugin individually with engine.LoadPlugin. + if err = allplugins.LoadAll(engine); err != nil { + log.Fatalf("Failed to load plugins: %v", err) + } // Build and start the workflows if err = engine.BuildFromConfig(cfg); err != nil { diff --git a/plugins/all/all.go b/plugins/all/all.go new file mode 100644 index 00000000..4ca17ce0 --- /dev/null +++ b/plugins/all/all.go @@ -0,0 +1,101 @@ +// Package all provides a single import point for all built-in workflow engine plugins. +// Applications that embed the workflow engine can call [LoadAll] to register every +// standard plugin in one step instead of importing and wiring each plugin package +// individually. +// +// Example – minimal embedded engine setup: +// +// engine := workflow.NewStdEngine(app, logger) +// if err := all.LoadAll(engine); err != nil { +// log.Fatalf("failed to load plugins: %v", err) +// } +// +// If you need finer control (e.g. to skip a plugin or add your own), use +// [DefaultPlugins] to obtain the slice and modify it before loading: +// +// plugins := all.DefaultPlugins() +// plugins = append(plugins, myCustomPlugin) +// for _, p := range plugins { +// engine.LoadPlugin(p) +// } +package all + +import ( + "github.com/GoCodeAlone/workflow/plugin" + pluginai "github.com/GoCodeAlone/workflow/plugins/ai" + pluginapi "github.com/GoCodeAlone/workflow/plugins/api" + pluginauth "github.com/GoCodeAlone/workflow/plugins/auth" + plugincicd "github.com/GoCodeAlone/workflow/plugins/cicd" + plugincloud "github.com/GoCodeAlone/workflow/plugins/cloud" + plugindatastores "github.com/GoCodeAlone/workflow/plugins/datastores" + plugindlq "github.com/GoCodeAlone/workflow/plugins/dlq" + pluginevstore "github.com/GoCodeAlone/workflow/plugins/eventstore" + pluginff "github.com/GoCodeAlone/workflow/plugins/featureflags" + plugingitlab "github.com/GoCodeAlone/workflow/plugins/gitlab" + pluginhttp "github.com/GoCodeAlone/workflow/plugins/http" + pluginintegration "github.com/GoCodeAlone/workflow/plugins/integration" + pluginlicense "github.com/GoCodeAlone/workflow/plugins/license" + pluginmarketplace "github.com/GoCodeAlone/workflow/plugins/marketplace" + pluginmessaging "github.com/GoCodeAlone/workflow/plugins/messaging" + pluginmodcompat "github.com/GoCodeAlone/workflow/plugins/modularcompat" + pluginobs "github.com/GoCodeAlone/workflow/plugins/observability" + pluginpipeline "github.com/GoCodeAlone/workflow/plugins/pipelinesteps" + pluginplatform "github.com/GoCodeAlone/workflow/plugins/platform" + pluginpolicy "github.com/GoCodeAlone/workflow/plugins/policy" + pluginscheduler "github.com/GoCodeAlone/workflow/plugins/scheduler" + pluginsecrets "github.com/GoCodeAlone/workflow/plugins/secrets" + pluginsm "github.com/GoCodeAlone/workflow/plugins/statemachine" + pluginstorage "github.com/GoCodeAlone/workflow/plugins/storage" + plugintimeline "github.com/GoCodeAlone/workflow/plugins/timeline" +) + +// PluginLoader is the minimal interface required by [LoadAll]. +// *workflow.StdEngine satisfies this interface. +type PluginLoader interface { + LoadPlugin(p plugin.EnginePlugin) error +} + +// DefaultPlugins returns the standard set of built-in engine plugins. +// The slice is freshly allocated on each call so callers may safely append +// custom plugins without affecting other callers. +func DefaultPlugins() []plugin.EnginePlugin { + return []plugin.EnginePlugin{ + pluginlicense.New(), + pluginhttp.New(), + pluginobs.New(), + pluginmessaging.New(), + pluginsm.New(), + pluginauth.New(), + pluginstorage.New(), + pluginapi.New(), + pluginpipeline.New(), + plugincicd.New(), + pluginff.New(), + pluginevstore.New(), + plugintimeline.New(), + plugindlq.New(), + pluginsecrets.New(), + pluginmodcompat.New(), + pluginscheduler.New(), + pluginintegration.New(), + pluginai.New(), + pluginplatform.New(), + plugincloud.New(), + plugingitlab.New(), + plugindatastores.New(), + pluginpolicy.New(), + pluginmarketplace.New(), + } +} + +// LoadAll loads all default built-in plugins into the given engine. +// It is equivalent to calling engine.LoadPlugin for each plugin returned by +// [DefaultPlugins]. The first error encountered is returned immediately. +func LoadAll(engine PluginLoader) error { + for _, p := range DefaultPlugins() { + if err := engine.LoadPlugin(p); err != nil { + return err + } + } + return nil +} diff --git a/plugins/all/all_test.go b/plugins/all/all_test.go new file mode 100644 index 00000000..0da47505 --- /dev/null +++ b/plugins/all/all_test.go @@ -0,0 +1,124 @@ +package all + +import ( + "testing" + + "github.com/GoCodeAlone/workflow/capability" + "github.com/GoCodeAlone/workflow/plugin" + "github.com/GoCodeAlone/workflow/schema" +) + +func TestDefaultPlugins_NotEmpty(t *testing.T) { + plugins := DefaultPlugins() + if len(plugins) == 0 { + t.Fatal("DefaultPlugins() returned empty slice") + } +} + +func TestDefaultPlugins_AllNonNil(t *testing.T) { + for i, p := range DefaultPlugins() { + if p == nil { + t.Errorf("DefaultPlugins()[%d] is nil", i) + } + } +} + +func TestDefaultPlugins_UniqueNames(t *testing.T) { + seen := make(map[string]bool) + for _, p := range DefaultPlugins() { + name := p.Name() + if seen[name] { + t.Errorf("duplicate plugin name %q in DefaultPlugins()", name) + } + seen[name] = true + } +} + +func TestDefaultPlugins_IndependentSlices(t *testing.T) { + a := DefaultPlugins() + b := DefaultPlugins() + if len(a) != len(b) { + t.Fatalf("successive calls returned different lengths: %d vs %d", len(a), len(b)) + } + // Modifying one slice must not affect the other. + a[0] = nil + if b[0] == nil { + t.Error("modifying slice returned by DefaultPlugins() affected a separate call") + } +} + +// stubEngine is a minimal PluginLoader used in tests to avoid importing the +// workflow package (which would create a circular dependency). +type stubEngine struct { + loaded []string + errOn string // if non-empty, return an error when this plugin name is loaded +} + +func (s *stubEngine) LoadPlugin(p plugin.EnginePlugin) error { + if s.errOn != "" && p.Name() == s.errOn { + return &testError{p.Name()} + } + s.loaded = append(s.loaded, p.Name()) + return nil +} + +type testError struct{ name string } + +func (e *testError) Error() string { return "test error for " + e.name } + +func TestLoadAll_LoadsAllPlugins(t *testing.T) { + eng := &stubEngine{} + if err := LoadAll(eng); err != nil { + t.Fatalf("LoadAll() unexpected error: %v", err) + } + + want := len(DefaultPlugins()) + if len(eng.loaded) != want { + t.Errorf("LoadAll() loaded %d plugins, want %d", len(eng.loaded), want) + } +} + +func TestLoadAll_ReturnsFirstError(t *testing.T) { + plugins := DefaultPlugins() + if len(plugins) == 0 { + t.Skip("no plugins") + } + // Trigger an error on the second plugin to verify early return. + targetName := plugins[1].Name() + eng := &stubEngine{errOn: targetName} + + err := LoadAll(eng) + if err == nil { + t.Fatal("LoadAll() expected error, got nil") + } + + // Only the first plugin should have been loaded (the second triggered the error). + if len(eng.loaded) != 1 { + t.Errorf("LoadAll() loaded %d plugins before error, want 1", len(eng.loaded)) + } +} + +// TestLoadAll_WithRealLoader verifies that all default plugins can be loaded +// into a real PluginLoader without conflicts. +func TestLoadAll_WithRealLoader(t *testing.T) { + capReg := capability.NewRegistry() + schemaReg := schema.NewModuleSchemaRegistry() + loader := plugin.NewPluginLoader(capReg, schemaReg) + + for _, p := range DefaultPlugins() { + if err := loader.LoadPlugin(p); err != nil { + t.Fatalf("LoadPlugin(%q) error: %v", p.Name(), err) + } + } + + // All plugins were loaded — there should be module, step, and trigger factories. + if len(loader.ModuleFactories()) == 0 { + t.Error("no module factories registered after loading all plugins") + } + if len(loader.StepFactories()) == 0 { + t.Error("no step factories registered after loading all plugins") + } + if len(loader.TriggerFactories()) == 0 { + t.Error("no trigger factories registered after loading all plugins") + } +}