feat(01-02): add optional plugin services and HasPlugin
- App-scoped typed Publish/Lookup with duplicate-provider errors - Set plugin IDs before Register so HasPlugin sees the full set - Greeter uses pact.OptionalMessage without importing optional
This commit is contained in:
@@ -1,13 +1,71 @@
|
||||
package backpack
|
||||
|
||||
import "git.golem15.com/golem15/summercms/compass"
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"git.golem15.com/golem15/summercms/compass"
|
||||
)
|
||||
|
||||
// App is the per-instance application container. It must not import party.
|
||||
type App struct {
|
||||
Config *compass.Config
|
||||
Services *Registry
|
||||
|
||||
mu sync.RWMutex
|
||||
plugins map[string]struct{}
|
||||
}
|
||||
|
||||
// New returns an App that owns cfg.
|
||||
func New(cfg *compass.Config) *App {
|
||||
return &App{Config: cfg}
|
||||
return &App{
|
||||
Config: cfg,
|
||||
Services: NewRegistry(),
|
||||
plugins: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// SetPlugins records the complete activated plugin set. HasPlugin uses this
|
||||
// list, including plugins that have not yet Booted.
|
||||
func (a *App) SetPlugins(ids []string) {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
next := make(map[string]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
if id != "" {
|
||||
next[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
a.mu.Lock()
|
||||
a.plugins = next
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
// HasPlugin reports whether id is in the complete registered set.
|
||||
func (a *App) HasPlugin(id string) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
_, ok := a.plugins[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Publish stores an app-scoped service under T.
|
||||
func (a *App) Publish[T any](value T) error {
|
||||
if a == nil || a.Services == nil {
|
||||
return fmt.Errorf("backpack: app has no service registry")
|
||||
}
|
||||
return a.Services.Publish(value)
|
||||
}
|
||||
|
||||
// Lookup returns the app-scoped service published under T.
|
||||
func (a *App) Lookup[T any]() (T, bool) {
|
||||
var zero T
|
||||
if a == nil || a.Services == nil {
|
||||
return zero, false
|
||||
}
|
||||
return a.Services.Lookup[T]()
|
||||
}
|
||||
|
||||
60
backpack/services.go
Normal file
60
backpack/services.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package backpack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Registry is an app-scoped typed service catalog. It has no process-global state.
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
m map[reflect.Type]any
|
||||
}
|
||||
|
||||
// NewRegistry returns an empty service registry.
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{m: make(map[reflect.Type]any)}
|
||||
}
|
||||
|
||||
// Publish stores value under the concrete type argument T. Publishing the
|
||||
// same T twice is an error.
|
||||
func (r *Registry) Publish[T any](value T) error {
|
||||
if r == nil {
|
||||
return fmt.Errorf("backpack: registry is nil")
|
||||
}
|
||||
if any(value) == nil {
|
||||
return fmt.Errorf("backpack: publish nil service")
|
||||
}
|
||||
key := reflect.TypeFor[T]()
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.m == nil {
|
||||
r.m = make(map[reflect.Type]any)
|
||||
}
|
||||
if _, exists := r.m[key]; exists {
|
||||
return fmt.Errorf("backpack: duplicate provider for %s", key)
|
||||
}
|
||||
r.m[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lookup returns the service published under T, or (zero, false).
|
||||
func (r *Registry) Lookup[T any]() (T, bool) {
|
||||
var zero T
|
||||
if r == nil {
|
||||
return zero, false
|
||||
}
|
||||
key := reflect.TypeFor[T]()
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
v, ok := r.m[key]
|
||||
if !ok {
|
||||
return zero, false
|
||||
}
|
||||
got, ok := v.(T)
|
||||
if !ok {
|
||||
return zero, false
|
||||
}
|
||||
return got, true
|
||||
}
|
||||
@@ -8,6 +8,7 @@ require (
|
||||
git.golem15.com/golem15/summercms v0.0.0
|
||||
git.golem15.com/golem15/summercms/examples/hello/plugins/base v0.0.0
|
||||
git.golem15.com/golem15/summercms/examples/hello/plugins/greeter v0.0.0
|
||||
git.golem15.com/golem15/summercms/examples/hello/plugins/optional v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -32,4 +33,5 @@ replace (
|
||||
git.golem15.com/golem15/summercms => ../..
|
||||
git.golem15.com/golem15/summercms/examples/hello/plugins/base => ./plugins/base
|
||||
git.golem15.com/golem15/summercms/examples/hello/plugins/greeter => ./plugins/greeter
|
||||
git.golem15.com/golem15/summercms/examples/hello/plugins/optional => ./plugins/optional
|
||||
)
|
||||
|
||||
@@ -5,10 +5,12 @@ package main
|
||||
import (
|
||||
_ "git.golem15.com/golem15/summercms/examples/hello/plugins/base"
|
||||
_ "git.golem15.com/golem15/summercms/examples/hello/plugins/greeter"
|
||||
_ "git.golem15.com/golem15/summercms/examples/hello/plugins/optional"
|
||||
)
|
||||
|
||||
// PluginIDs is the ordered plugin list from summer.yaml.
|
||||
var PluginIDs = []string{
|
||||
"golem15.hello",
|
||||
"golem15.greeter",
|
||||
"golem15.optional",
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@ import (
|
||||
"io/fs"
|
||||
|
||||
"git.golem15.com/golem15/summercms/backpack"
|
||||
"git.golem15.com/golem15/summercms/pact"
|
||||
"git.golem15.com/golem15/summercms/party"
|
||||
)
|
||||
|
||||
var _ pact.HasConfig = (*Plugin)(nil)
|
||||
|
||||
//go:embed config
|
||||
var configFS embed.FS
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@ import (
|
||||
|
||||
"git.golem15.com/golem15/summercms/backpack"
|
||||
"git.golem15.com/golem15/summercms/bonfire"
|
||||
"git.golem15.com/golem15/summercms/pact"
|
||||
"git.golem15.com/golem15/summercms/party"
|
||||
)
|
||||
|
||||
// Plugin is the golem15.greeter plugin. It requires golem15.hello.
|
||||
type Plugin struct {
|
||||
app *backpack.App
|
||||
extra string
|
||||
}
|
||||
|
||||
func (p *Plugin) ID() string { return "golem15.greeter" }
|
||||
@@ -20,6 +22,12 @@ func (p *Plugin) Register(*backpack.App) error { return nil }
|
||||
|
||||
func (p *Plugin) Boot(app *backpack.App) error {
|
||||
p.app = app
|
||||
p.extra = ""
|
||||
if app != nil && app.HasPlugin("golem15.optional") {
|
||||
if msg, ok := app.Lookup[pact.OptionalMessage](); ok {
|
||||
p.extra = msg.Message()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -36,12 +44,14 @@ func (p *Plugin) Commands() []bonfire.Command {
|
||||
posts = p.app.Config.Int("golem15.hello.posts_per_page")
|
||||
debug = p.app.Config.Bool("app.debug")
|
||||
}
|
||||
out.Printf("name=%s posts_per_page=%d debug=%t\n", name, posts, debug)
|
||||
out.Printf("name=%s posts_per_page=%d debug=%t extra=%s\n", name, posts, debug, p.extra)
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
var _ pact.HasCommands = (*Plugin)(nil)
|
||||
|
||||
func init() {
|
||||
party.Register(&Plugin{})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -61,6 +62,16 @@ func TestBootUsesPublishedOptionalService(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGreeterSourceDoesNotImportOptional(t *testing.T) {
|
||||
body, err := os.ReadFile("plugin.go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(string(body), "plugins/optional") {
|
||||
t.Fatal("greeter must not import the optional plugin module")
|
||||
}
|
||||
}
|
||||
|
||||
func runHelloCmd(t *testing.T, p *Plugin) string {
|
||||
t.Helper()
|
||||
cmds := p.Commands()
|
||||
|
||||
1
examples/hello/plugins/optional/config/config.yaml
Normal file
1
examples/hello/plugins/optional/config/config.yaml
Normal file
@@ -0,0 +1 @@
|
||||
enabled: true
|
||||
27
examples/hello/plugins/optional/go.mod
Normal file
27
examples/hello/plugins/optional/go.mod
Normal file
@@ -0,0 +1,27 @@
|
||||
module git.golem15.com/golem15/summercms/examples/hello/plugins/optional
|
||||
|
||||
go 1.27.0
|
||||
|
||||
toolchain go1.27.0
|
||||
|
||||
require git.golem15.com/golem15/summercms v0.0.0
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.1 // indirect
|
||||
github.com/knadh/koanf/providers/confmap v1.0.1 // indirect
|
||||
github.com/knadh/koanf/providers/env/v2 v2.0.1 // indirect
|
||||
github.com/knadh/koanf/providers/file v1.2.1 // indirect
|
||||
github.com/knadh/koanf/v2 v2.3.6 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/spf13/cobra v1.10.2 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
)
|
||||
|
||||
replace git.golem15.com/golem15/summercms => ../../../..
|
||||
47
examples/hello/plugins/optional/go.sum
Normal file
47
examples/hello/plugins/optional/go.sum
Normal file
@@ -0,0 +1,47 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.1 h1:u70vV5IyaM0HvONh8HoqBC97oTgO33KcpZbTLiKVinU=
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.1/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
|
||||
github.com/knadh/koanf/providers/confmap v1.0.1 h1:L15hbvMqlvhwUuCtL9BkL+rqiMAjk6cZc8O9XoDtE3A=
|
||||
github.com/knadh/koanf/providers/confmap v1.0.1/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
|
||||
github.com/knadh/koanf/providers/env/v2 v2.0.1 h1:a3KagndPqhcWHQv6Pz4OZmwkI/yMeTjkiZye6ZCkyW0=
|
||||
github.com/knadh/koanf/providers/env/v2 v2.0.1/go.mod h1:1g01PE+Ve1gBfWNNw2wmULRP0tc8RJrjn5p2N/jNCIc=
|
||||
github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM=
|
||||
github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||
github.com/knadh/koanf/v2 v2.3.6 h1:JoQPSJmvS4aP0xNc8xMDr5tcrkSEInL23/Il7pITAKo=
|
||||
github.com/knadh/koanf/v2 v2.3.6/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
43
examples/hello/plugins/optional/plugin.go
Normal file
43
examples/hello/plugins/optional/plugin.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package optional
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
|
||||
"git.golem15.com/golem15/summercms/backpack"
|
||||
"git.golem15.com/golem15/summercms/pact"
|
||||
"git.golem15.com/golem15/summercms/party"
|
||||
)
|
||||
|
||||
//go:embed config
|
||||
var configFS embed.FS
|
||||
|
||||
// Plugin is the golem15.optional plugin. Greeter may use it without importing this package.
|
||||
type Plugin struct{}
|
||||
|
||||
func (p *Plugin) ID() string { return "golem15.optional" }
|
||||
func (p *Plugin) Requires() []string { return nil }
|
||||
|
||||
func (p *Plugin) Register(app *backpack.App) error {
|
||||
if app == nil {
|
||||
return nil
|
||||
}
|
||||
return app.Publish[pact.OptionalMessage](extra{})
|
||||
}
|
||||
|
||||
func (p *Plugin) Boot(*backpack.App) error { return nil }
|
||||
|
||||
func (p *Plugin) ConfigFS() fs.FS { return configFS }
|
||||
|
||||
type extra struct{}
|
||||
|
||||
func (extra) Message() string { return "hello-from-optional" }
|
||||
|
||||
var (
|
||||
_ pact.HasConfig = (*Plugin)(nil)
|
||||
_ pact.OptionalMessage = extra{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
party.Register(&Plugin{})
|
||||
}
|
||||
@@ -5,3 +5,5 @@ plugins:
|
||||
module: git.golem15.com/golem15/summercms/examples/hello/plugins/base
|
||||
- id: golem15.greeter
|
||||
module: git.golem15.com/golem15/summercms/examples/hello/plugins/greeter
|
||||
- id: golem15.optional
|
||||
module: git.golem15.com/golem15/summercms/examples/hello/plugins/optional
|
||||
|
||||
1
go.work
1
go.work
@@ -7,4 +7,5 @@ use (
|
||||
./examples/hello
|
||||
./examples/hello/plugins/base
|
||||
./examples/hello/plugins/greeter
|
||||
./examples/hello/plugins/optional
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestLoadHelloManifest(t *testing.T) {
|
||||
if m.Binary != "hello" {
|
||||
t.Fatalf("binary = %q", m.Binary)
|
||||
}
|
||||
if len(m.Plugins) != 2 || m.Plugins[0].ID != "golem15.hello" || m.Plugins[1].ID != "golem15.greeter" {
|
||||
if len(m.Plugins) != 3 || m.Plugins[0].ID != "golem15.hello" || m.Plugins[1].ID != "golem15.greeter" || m.Plugins[2].ID != "golem15.optional" {
|
||||
t.Fatalf("plugins = %+v", m.Plugins)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,16 @@ type HasConfig interface {
|
||||
ConfigFS() fs.FS
|
||||
}
|
||||
|
||||
// Reserved KERN-03 capability names, declared when their first consumers exist:
|
||||
// OptionalMessage is a service an optional plugin may publish so other
|
||||
// plugins can integrate without importing that plugin's package.
|
||||
type OptionalMessage interface {
|
||||
Message() string
|
||||
}
|
||||
|
||||
// Future KERN-03 capability families are type-asserted when their first
|
||||
// consumer packages exist. Method payloads are not declared here so pact
|
||||
// does not import surf, lagoon, festival, or other packages that Phase 1
|
||||
// does not yet ship:
|
||||
//
|
||||
// HasModels
|
||||
// HasMigrations
|
||||
@@ -32,3 +41,6 @@ type HasConfig interface {
|
||||
// HasSchedule
|
||||
// HasMailTemplates
|
||||
// HasLang
|
||||
//
|
||||
// Phase 1 type-asserts only HasConfig (party, before Register) and
|
||||
// HasCommands (generated app main, after Boot).
|
||||
|
||||
@@ -98,6 +98,12 @@ func activate(registered []Plugin, app *backpack.App, ids []string) ([]Plugin, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderedIDs := make([]string, len(ordered))
|
||||
for i, p := range ordered {
|
||||
orderedIDs[i] = p.ID()
|
||||
}
|
||||
app.SetPlugins(orderedIDs)
|
||||
|
||||
for _, p := range ordered {
|
||||
if hc, ok := p.(pact.HasConfig); ok && app.Config != nil {
|
||||
if err := app.Config.MergePlugin(p.ID(), hc.ConfigFS()); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user