feat(build): summer plugin:install for registry plugins as Go modules

- kiosk.Client reads the registry API v1 over fetchguard (trusted mode,
  4 MiB, 10 s, no redirects); kiosk.RegistryURL reads plugins.registry_url
  (default https://summercms.io, empty disables)
- build.PlanInstall/ApplyInstall pick the newest release the effective
  framework satisfies (or a pin), validate registry strings, go get it and
  append it to summer.yaml
- summer.yaml edits (plugin:add and plugin:install) keep comments and
  blank lines
- TestInstallSmoke/module installs from a file:// GOPROXY and builds
This commit is contained in:
Jakub Zych
2026-10-29 18:22:01 +01:00
parent e7eda88ddd
commit 9b23e877a6
10 changed files with 1323 additions and 4 deletions

View File

@@ -109,6 +109,7 @@ summer plugin:add plugins/acme/blog
|---------|---------|
| `summer plugin:add` | Registers a local plugin directory: `summer.yaml`, a `require` and a local `replace` in `go.mod`, and `go.work`. |
| `summer plugin:manifest` | Writes the plugin's `plugin.yaml` from its sources (`plugin.go`, `go.mod`, the README). `--pretend` prints it, `--check` exits 1 when the committed file is missing or stale, `--name` and `--description` override the README text. See [Publishing plugins](../plugins/marketplace.md). |
| `summer plugin:install` | Installs a plugin by ID (`acme.blog`, or pinned `acme.blog@v0.3.0`) from the registry as a Go module: the newest release the application's framework version meets, after printing it and asking (`--yes` skips the question). See [Installing plugins](../plugins/marketplace.md#installing-plugins). |
## Generating plugin parts

View File

@@ -1,6 +1,6 @@
---
title: Publishing plugins
description: Describe a plugin for the marketplace with a generated plugin.yaml, keep it current with summer plugin:manifest --check, and read the registry API.
description: Describe a plugin with a generated plugin.yaml, install listed plugins with summer plugin:install, and read the registry API.
section: plugins
order: 50
---
@@ -92,6 +92,35 @@ func TestPluginManifest(t *testing.T) {
`summer build` reads the `plugin.yaml` of every plugin the application lists and bakes the name and description into the generated `plugins.gen.go` as `PluginInfo`, next to each plugin's version and the framework version (`FrameworkVersion`). At start-up the application publishes them as `party.Installed`, which the admin's Plugins screen reads. [Go modules and workspaces](../architecture/go-modules-and-workspaces.md#the-summeryaml-manifest) explains where the versions come from.
## Installing plugins
`summer plugin:install` adds a listed plugin to an application. Run it inside the application:
```sh
summer plugin:install acme.blog
summer plugin:install acme.blog@v0.3.0
summer plugin:install acme.blog --yes
```
The command asks the registry for the plugin's releases and picks the newest one whose framework minimum the application meets. The application's framework version is the effective one: the newest tag of a local framework checkout when `go.mod` or `go.work` replaces the framework with a directory, otherwise the version `go.mod` requires. `@vX.Y.Z` pins a release, which must meet the framework minimum too. When every release needs a newer framework, the command stops and names the framework version to move to; it never changes the framework requirement itself.
The plugin is installed as a Go module: one `go get <module>@<version>` in the application, then an entry with its ID and package in `summer.yaml`. The edit appends to the `plugins:` list and keeps the file's comments and blank lines. Run `summer build` afterwards: it generates the import, and the new require in `go.mod` loses its `// indirect` marker the next time you run `go mod tidy`. A plugin that is already in `summer.yaml` is not installed again.
Before changing anything, the command prints each plugin it is about to add as `id@version (module)` and asks for confirmation. `--yes` skips the question. A run that cannot ask (no terminal, `-n` or `-q`) stops with an error unless `--yes` is given.
### The registry setting
The registry is `https://summercms.io` unless the application's `plugins.registry_url` setting names another one:
```yaml
# config/plugins.yaml
registry_url: https://registry.example.com
```
The environment variable `SUMMER_PLUGINS__REGISTRY_URL` overrides the file. The value must be the final origin, because the client does not follow redirects. An empty value disables registry calls: installs by ID then fail, and the admin's Plugins screen shows only installed plugins. `--offline` disables registry calls for one run.
Every registry call goes through `kiosk.Client`, which uses `fetchguard` with a 10 second timeout and a 4 MiB limit on the answer. Module paths, package paths and versions from the registry are checked (a plain module path, a strict `vX.Y.Z` version) before any `go` command runs.
## Registry API v1
summercms.io publishes the plugins it lists through a read-only JSON API. The marketplace pages, `summer` and the admin's Plugins screen read it. Its base path is `/api/registry/v1` (`kiosk.RegistryAPIPath`), and the `kiosk` module holds its wire types, so every client and the server share one definition of the shape.