- 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
7.2 KiB
title, description, section, order
| title | description | section | order |
|---|---|---|---|
| Go modules and workspaces | Require the framework module, see what summer new writes, develop plugins in a Go workspace, list them in summer.yaml and fork a plugin. | architecture | 20 |
Go modules and workspaces
WinterCMS uses Composer to pull in the framework and plugins. SummerCMS uses Go modules: the framework is one module, each plugin is its own module, and the application module requires them all.
The framework module
The framework is the module git.golem15.com/golem15/summercms. Every framework package is imported from git.golem15.com/golem15/summercms/modules/<name>, for example git.golem15.com/golem15/summercms/modules/party.
An application requires the framework in its go.mod. While you work against a local checkout of the framework, point the requirement at it with a replace directive. For an application module acme in a directory next to the framework checkout:
module git.golem15.com/acme/acme
go 1.27.0
require git.golem15.com/golem15/summercms v0.0.0
replace git.golem15.com/golem15/summercms => ../summercms.go
summer make:plugin copies this framework replace into the new plugin's go.mod, rewritten relative to the plugin directory, so the plugin builds against the same checkout.
Applications created by summer new
summer new acme writes these files for you in sm-acme-app, next to the framework checkout. The application module is git.golem15.com/golem15/sm-acme-app, and its go.mod replaces the framework with a relative path, so the directory pair can move together or be cloned elsewhere:
module git.golem15.com/golem15/sm-acme-app
go 1.27.0
toolchain go1.27.0
require git.golem15.com/golem15/summercms v0.0.0
replace git.golem15.com/golem15/summercms => ../summercms
The starter plugin golem15.acme is added through summer plugin:add, so the same go.mod also gets a require of git.golem15.com/golem15/sm-acme-app/plugins/acme and a replace pointing at ./plugins/acme.
summer new picks the framework checkout in this order:
- the path given with
--framework, relative to the current directory or absolute; a wrong path is an error, not a reason to keep looking; - a sibling directory named
summercms, thensummercms.go; - the source tree the
summerbinary was built from, when it was built from a checkout without-trimpath.
Every candidate counts only when its go.mod declares the module git.golem15.com/golem15/summercms; the directory name alone is not enough. Without a match, summer new stops and asks for --framework.
The application also gets a go.work that is committed with it. It lists the application itself and, unless --no-plugin was given, the starter plugin, both as relative use paths:
go 1.27.0
toolchain go1.27.0
use (
.
./plugins/acme
)
There is no go.sum in the application root: builds go through the workspace, and go.work.sum, which Go writes on the first build, is listed in .gitignore. The starter plugin keeps its own go.mod and go.sum like any other plugin module.
The summer.yaml manifest
The manifest in the application root names the application module, the binary summer build writes to bin/, the plugins in activation order and, optionally, Go build tags:
module: git.golem15.com/acme/acme
binary: acme
plugins:
- id: acme.user
module: git.golem15.com/acme/acme/plugins/acme/user
- id: acme.blog
module: git.golem15.com/acme/acme/plugins/acme/blog
build:
tags:
- summercms_s3
summer build turns this list into plugins.gen.go. The order is the manifest order, adjusted so that each plugin comes after the plugins it requires. Plugins with no dependency between them keep the order you wrote.
plugins.gen.go also carries what the build knew about each plugin and the framework:
PluginInfo, oneparty.Infoper plugin in manifest order: the ID, the Go module and package, the name and description from the plugin'splugin.yaml(see Publishing plugins), the version and the source. A plugin from the module cache (go get) reports its module version and the sourcemodule. A plugin directory that is its own git repository, such as a submodule, reportsgit describe --tagsoutput likev0.3.1-4-gabc1234and the sourcesubmodule; an uncommitted change does not alter it. A plugin inside the application's own repository has no version and the sourcelocal. A plugin withoutplugin.yamlhas no name or description.FrameworkVersion, the framework version the application was built against. Whengo.modorgo.workpoints the framework at a local checkout, it is the newest tag reachable in that checkout; otherwise it is the versiongo.modrequires.
The generated main publishes both with party.Describe before activating the plugins, so the admin can show the installed plugins with app.Lookup[party.Installed](). When the metadata cannot be resolved, summer build prints a warning and bakes the IDs only; it never fails a build. A submodule bump or a new framework tag therefore changes plugins.gen.go: commit the regenerated file with the bump.
build.tags lists Go build tags that summer build (and summer dev) passes to go build as -tags=<tag>,<tag>, printing them as build tags: …. Each tag must consist of lower-case letters, digits and underscores; a duplicate is ignored. Framework features that pull in large dependencies sit behind a tag, such as summercms_s3 for S3 uploads buckets (see Storage). summer plugin:add keeps the list when it rewrites the manifest. A script that runs its own go build must pass the same tags.
Local plugins in a workspace
A plugin you develop inside the application lives in plugins/<vendor>/<name> as its own module. summer make:plugin acme.blog creates it in plugins/acme/blog, and summer plugin:add plugins/acme/blog registers it:
- it adds the plugin to
summer.yaml; - it adds a
requireand areplacepointing at the local directory to the application'sgo.mod; - it adds the plugin directory to the nearest
go.workas a path relative to that file, such as./plugins/acme/blog, creating ago.workin the application root when there is none.
With a go.work in place, go build, go test and your editor see every local plugin module together. summer build uses the nearest go.work it finds; without one it builds in module mode.
summer make:plugin acme.blog
summer plugin:add plugins/acme/blog
summer build
Replacing and forking a plugin
WinterCMS lets you replace a plugin by overriding its classes. In SummerCMS you fork the plugin's module and point the application at your fork with a replace directive. The plugin ID and the import path stay the same, so nothing else in the application changes:
replace git.golem15.com/acme/user => ../forks/user
Keep the fork's plugin ID unchanged when it must stand in for the original, since other plugins require it by ID. If you want both to exist side by side, give the fork a new module path and a new ID and list it in summer.yaml instead. To extend a plugin without forking it, see Extending plugins.