feat(kiosk): resolve constant plugin IDs, overrides and the CheckT helper

- kiosk.ReadIdentity: ID()/Requires() as literals or constants across packages and modules, read with go/parser, never run
- plugin minimums from go.mod requires; v0.0.0 is an error, a pseudo-version warns
- kiosk.Validate, kiosk.CheckT (no testing import)
- plugin:add reads IDs through kiosk.ReadIdentity, so const-ID plugins register
This commit is contained in:
Jakub Zych
2026-10-29 17:02:38 +01:00
parent ec6a1955fc
commit 51ec2f9daa
22 changed files with 1156 additions and 115 deletions

View File

@@ -15,10 +15,11 @@ WinterCMS keeps this data in a plugin's `Plugin.php` `pluginDetails()` and in `c
## Features
- One `plugin.yaml` per plugin package directory, so a repository with a nested plugin carries two files.
- `kiosk.ReadIdentity` reads `ID()` and `Requires()` statically with `go/parser`: a string literal, a constant of the same package, of another package of the module, or of a package in a module `go.mod` requires. It never builds or runs plugin code, and `summer plugin:add` reads plugin IDs with it too.
- A schema version (`kiosk.SchemaVersion`) and a fixed header line (`kiosk.Header`); a manifest with a newer schema is refused with a hint to update summer.
- Go-style minimums: `framework` is the `go.mod` require of `kiosk.FrameworkModule`, and each required plugin gets the version its module is required at.
- Go-style minimums: `framework` is the `go.mod` require of `kiosk.FrameworkModule`, and each required plugin gets the version its module is required at. A requirement at `v0.0.0` is an error (not a released version); a pseudo-version is accepted with a warning.
- Name and description overrides (`--name`, `--description`) stored in an `overrides:` block that regeneration keeps.
- `kiosk.Check` regenerates in memory and returns a `kiosk.StaleError` when the committed file is missing or differs, which `summer plugin:manifest --check` turns into exit status 1.
- `kiosk.Check` regenerates in memory and returns a `kiosk.StaleError` when the committed file is missing or differs, which `summer plugin:manifest --check` turns into exit status 1, and `kiosk.CheckT` turns into a failing test in the plugin's own `go test`.
## Usage
@@ -41,6 +42,14 @@ if err := kiosk.Check(ctx, "plugins/acme/blog"); err != nil {
}
```
A plugin repository keeps its manifest current with one test:
```go
func TestPluginManifest(t *testing.T) {
kiosk.CheckT(t, ".")
}
```
## API reference
| Identifier | Description |
@@ -60,6 +69,12 @@ if err := kiosk.Check(ctx, "plugins/acme/blog"); err != nil {
| `kiosk.Generate` | `kiosk.Derive` plus `kiosk.Marshal`: the bytes `kiosk.Write` would write. |
| `kiosk.Write` | Generates and writes `<dir>/plugin.yaml` and returns its path. |
| `kiosk.Check` | Returns a `kiosk.StaleError` when `<dir>/plugin.yaml` is missing or differs from a fresh generation. |
| `kiosk.StaleError.Error` | Names the path, and for a stale file the first differing line. |
| `kiosk.TB` | The part of `testing.TB` that `kiosk.CheckT` calls (`Helper`, `Fatalf`); kiosk does not import `testing`. |
| `kiosk.CheckT` | Fails the test with the `kiosk.Check` error and the command to rerun when the manifest is missing or stale. |
| `kiosk.Validate` | Checks a manifest: schema, ID and required IDs, module and package paths (no leading `-`), name, description, and `vX.Y.Z` or pseudo-version minimums. `kiosk.Parse` and `kiosk.Generate` call it. |
| `kiosk.Identity` | The plugin ID and the required plugin IDs. |
| `kiosk.ReadIdentity` | Reads what `ID()` and `Requires()` return from the sources of a directory, resolving constants across packages and modules without running code. Any other expression is an error naming the method. |
| `kiosk.StaleError` | `Path`, `Missing`, and the generated (`Want`) and committed (`Got`) bytes; its message names the first differing line. |
## CLI commands
@@ -72,8 +87,8 @@ if err := kiosk.Check(ctx, "plugins/acme/blog"); err != nil {
- SummerCMS modules: none.
- Third-party: `github.com/goccy/go-yaml` (manifest YAML), `github.com/yuin/goldmark` (README headings and paragraphs).
- Standard library: `bytes`, `context`, `encoding/json`, `errors`, `fmt`, `go/ast`, `go/parser`, `go/token`, `os`, `os/exec`, `path/filepath`, `regexp`, `sort`, `strconv`, `strings`.
- Tools: the `go` command (`go mod edit -json`), run without a shell.
- Standard library: `bytes`, `context`, `encoding/json`, `errors`, `fmt`, `go/ast`, `go/parser`, `go/token`, `go/types` (expression printing only), `os`, `os/exec`, `path/filepath`, `regexp`, `slices`, `sort`, `strconv`, `strings`.
- Tools: the `go` command (`go mod edit -json`, `go list -m -json`, and `go mod download -json` for a required module not in the module cache), run without a shell and with workspaces off.
## Testing
@@ -81,4 +96,4 @@ if err := kiosk.Check(ctx, "plugins/acme/blog"); err != nil {
go test ./modules/kiosk/...
```
`TestManifestSmoke` copies the fixture plugins in `testdata/` to a temporary directory and generates, checks and edits their manifests. The tests run the local `go` command but need no network and no database.
`TestManifestSmoke` copies the fixture plugins in `testdata/` to a temporary directory and generates, checks and edits their manifests: a literal-ID plugin, a plugin whose identity is spread over constants of three packages and two modules, a nested plugin, the error cases, overrides, `kiosk.CheckT` and `kiosk.Validate`. Every fixture module replaces the framework with the stub in `testdata/framework`, so the tests run the local `go` command but need no network and no database.