- module installs move to the newest compatible registry release with one go get; submodules fetch tags, check out the release tag and require it in go.mod; go.work overrides and version pins are refreshed - unlisted submodules (git URL installs) update from their own tags and plugin.yaml; --offline restricts to that - without an ID every plugin that resolves is updated; in-app, unlisted and incompatible ones are reported; failure only when a resolved update cannot be carried out - same prompt rule as plugin:install
135 lines
9.2 KiB
Markdown
135 lines
9.2 KiB
Markdown
---
|
|
title: Go modules and workspaces
|
|
description: Require the framework module, see what summer new writes, develop plugins in a Go workspace, list them in summer.yaml and fork a plugin.
|
|
section: architecture
|
|
order: 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
1. the path given with `--framework`, relative to the current directory or absolute; a wrong path is an error, not a reason to keep looking;
|
|
2. a sibling directory named `summercms`, then `summercms.go`;
|
|
3. the source tree the `summer` binary 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```yaml
|
|
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`, one `party.Info` per plugin in manifest order: the ID, the Go module and package, the name and description from the plugin's `plugin.yaml` (see [Publishing plugins](../plugins/marketplace.md)), the version and the source. A plugin from the module cache (`go get`) reports its module version and the source `module`. A plugin directory that is its own git repository, such as a submodule, reports `git describe --tags` output like `v0.3.1-4-gabc1234` and the source `submodule`; an uncommitted change does not alter it. A plugin inside the application's own repository has no version and the source `local`. A plugin without `plugin.yaml` has no name or description.
|
|
- `FrameworkVersion`, the framework version the application was built against. When `go.mod` or `go.work` points the framework at a local checkout, it is the newest tag reachable in that checkout; otherwise it is the version `go.mod` requires.
|
|
|
|
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](../services/storage.md#s3-and-s3-compatible-buckets)). `summer plugin:add` and `summer plugin:install` append their entries to the `plugins:` list in place, so the rest of the file, comments and blank lines included, stays as it was. 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 `require` and a `replace` pointing at the local directory to the application's `go.mod`;
|
|
- it adds the plugin directory to the nearest `go.work` as a path relative to that file, such as `./plugins/acme/blog`, creating a `go.work` in 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.
|
|
|
|
```sh
|
|
summer make:plugin acme.blog
|
|
summer plugin:add plugins/acme/blog
|
|
summer build
|
|
```
|
|
|
|
## Plugins installed as submodules
|
|
|
|
`summer plugin:install <id> --source` and `summer plugin:install <git-url>` check a plugin's repository out at a release tag as a git submodule in `plugins/<vendor>/<name>` and register it like `summer plugin:add`, with one difference: the `require` in `go.mod` names the tag (`v0.3.1`), not `v0.0.0`, so a build with `GOWORK=off` and the version checks of later installs see the real release. See [Installing plugins](../plugins/marketplace.md#installing-plugins).
|
|
|
|
A shared plugin's own `go.mod` usually replaces the framework, and sometimes sibling plugins, with paths of its development layout, such as `replace git.golem15.com/golem15/summercms => ../summercms.go`. Inside the application's workspace those paths do not exist, and two workspace modules that replace one module with different paths make Go stop with "conflicting replacements". A `replace` in `go.work` wins over the replaces in the workspace modules' `go.mod` files, so the installer adds one for every directory replace it finds in a workspace module's `go.mod` that `go.work` does not replace yet:
|
|
|
|
- when the application's `go.mod` replaces the same module (a local framework checkout, for example), `go.work` gets the same target;
|
|
- when the module is itself in the workspace (another plugin installed as a submodule), `go.work` replaces that module at its required version with its directory, such as `replace git.example.com/acme/translate v0.1.0 => ./plugins/acme/translate` (Go refuses a replace of a workspace module at all versions);
|
|
- otherwise `go.work` pins the module to the version the application requires, such as `replace git.golem15.com/golem15/summercms => git.golem15.com/golem15/summercms v0.6.0`.
|
|
|
|
A pin of the last kind follows the application: when the framework requirement in `go.mod` moves, the next `summer plugin:install` from source or `summer plugin:update` moves the pin to the new version. Replaces you wrote into `go.work` yourself are left alone.
|
|
|
|
## 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:
|
|
|
|
```text
|
|
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](../plugins/extending.md).
|