- party.Info, party.Installed, party.Describe (additive; Plugin and Activate unchanged) - summer build resolves plugin dirs with one go list, reads plugin.yaml, versions per D-07 (module version, git describe without --dirty, none for in-app plugins) - build.EffectiveFramework: newest tag of a locally replaced or used framework checkout, else the go.mod require - generated main calls party.Describe(app, PluginInfo, FrameworkVersion) before Activate; metadata failures warn and fall back to IDs - summer new goldens and examples/hello regenerated
5.8 KiB
party
Compiled plugin registry that orders plugins by their dependencies and runs their Register and Boot lifecycle.
import "git.golem15.com/golem15/summercms/modules/party"
Name: The guest list and the order people arrive in; it registers plugins and boots them in dependency order.
Overview
party is the WinterCMS PluginBase and PluginManager counterpart for plugins compiled into the binary. Each plugin package calls party.Register from its init function, and the application's generated main calls party.Activate with the plugin IDs listed in its manifest. Activation validates the selection, sorts it so every plugin comes after the plugins it requires, merges plugin config, runs every Register before any Boot, and wires translations, mail templates and menu item types in between. Capabilities beyond the lifecycle are declared through the interfaces in pact.
Role in the framework
The plugin lifecycle, on top of backpack, pact, phrasebook, postcard and signpost. lagoon, conga, cabana and surf ask it which plugins are active and in what order. It is the counterpart of WinterCMS's plugin manager, without autoloading: plugins are registered at build time.
Use cases:
- Declare that a plugin depends on another one, and have it registered and booted after it.
- Choose which compiled plugins are active through the manifest, with missing requirements and cycles caught at boot.
- Run Register before Boot, so plugins can wire services before they use each other.
Features
party.Plugin, the descriptor every plugin implements:party.Plugin.ID,party.Plugin.Requires,party.Plugin.Registerandparty.Plugin.Boot.- A process-wide, concurrency-safe registry filled by
party.Register(nil plugins are ignored). party.Activateselects plugins by manifest ID and fails on an empty ID, a duplicate ID, an unregistered plugin, a missing requirement or a dependency cycle.- Stable topological ordering: plugins without a dependency relation keep their manifest order.
- Build-time metadata:
party.Describepublishesparty.Installed, the plugin metadata (party.Info: ID, module, package, name, description, version and source) and the framework version thatsummer buildbaked into the application'splugins.gen.go. The generatedmaincalls it beforeparty.Activate. - Activation sequence: records the ordered IDs on the
backpack.App, publishesparty.Activated(the ordered plugin list for commands that need plugin scopes at Run time), merges eachpact.HasConfigtree into the app config under the plugin ID, runs every Register, publishes the translator (phrasebook), the mailer (postcard) and the menu item type registry (signpost, collected from everypact.HasMenuItemTypesplugin; a duplicate type key fails the activation), then registers each plugin's mail templates and runs its Boot.
Usage
A plugin registers itself when its package is imported:
package blog
import (
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/party"
)
type Plugin struct{}
func (p *Plugin) ID() string { return "acme.blog" }
func (p *Plugin) Requires() []string { return []string{"acme.user"} }
func (p *Plugin) Register(app *backpack.App) error { return nil }
func (p *Plugin) Boot(app *backpack.App) error { return nil }
func init() {
party.Register(&Plugin{})
}
The application publishes its build-time metadata and activates the plugins it lists, in dependency order (summer build generates both lists):
cfg, err := compass.Load("config")
if err != nil {
return err
}
app := backpack.New(cfg)
info := []party.Info{
{ID: "acme.user", Source: party.SourceLocal},
{ID: "acme.blog", Version: "v0.2.5", Source: party.SourceModule},
}
if err := party.Describe(app, info, "v0.6.0"); err != nil {
return err
}
plugins, err := party.Activate(app, []string{"acme.user", "acme.blog"})
if err != nil {
return err
}
API reference
| Identifier | Description |
|---|---|
party.Plugin |
Interface every compiled plugin implements: ID, required plugin IDs, Register and Boot. |
party.Register |
Adds a plugin to the process-wide registry; called from the plugin's init. |
party.Activate |
Selects registered plugins by ID, orders them by party.Plugin.Requires and runs the config, Register, translation, mail, menu item type and Boot steps; returns the ordered plugins. |
party.Info |
One plugin's build-time metadata: ID, Module, Package, Name, Description (from its plugin.yaml), Version and Source. |
party.SourceModule |
Source of a plugin built from the Go module cache; its version is the module version. |
party.SourceSubmodule |
Source of a plugin directory that is its own git repository; its version is git describe --tags output. |
party.SourceLocal |
Source of a plugin inside the application repository; it has no version. |
party.Installed |
What the application was built with: Framework and Plugins in manifest order; look it up with app.Lookup[party.Installed](). |
party.Describe |
Publishes party.Installed on the app; a nil app is an error. |
party.Activated |
The ordered []party.Plugin published by party.Activate; commands look it up at Run time with app.Lookup[party.Activated](). |
Dependencies
- SummerCMS modules: backpack, pact, phrasebook, postcard, signpost.
- Third-party: none.
- Standard library:
fmt,strings,sync.
Testing
go test ./modules/party/...
The tests use in-memory plugins and testing/fstest file systems and need no external services.