# kiosk Plugin manifests and the plugin registry contract: generates and checks each plugin's plugin.yaml from its own sources. `import "git.golem15.com/golem15/summercms/modules/kiosk"` **Name:** A beach kiosk where every plugin on the shelf carries its label; the label is printed from what is in the box, never written by hand. ## Overview A plugin describes itself in `plugin.yaml`, committed next to its `plugin.go`. The file is generated: `summer plugin:manifest` runs `kiosk.Write`, which reads the plugin's sources and never runs its code. The ID and the required plugins come from what `ID()` and `Requires()` in `plugin.go` return, the module path and the version minimums from `go.mod`, the package import path from the directory, and the name and description from the README's H1 and the paragraph after it (the standard README structure). The registry on summercms.io reads the same file from every tagged release, and `summer build` bakes its name and description into the application. WinterCMS keeps this data in a plugin's `Plugin.php` `pluginDetails()` and in `composer.json`; here it is derived once, so the copy in the repository cannot drift from the code. ## 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. 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, and `kiosk.CheckT` turns into a failing test in the plugin's own `go test`. ## Usage ```go path, warnings, err := kiosk.Write(ctx, "plugins/acme/blog", kiosk.DeriveOptions{}) if err != nil { return err } for _, w := range warnings { log.Println(w) } fmt.Println("wrote", path) if err := kiosk.Check(ctx, "plugins/acme/blog"); err != nil { var stale *kiosk.StaleError if errors.As(err, &stale) { fmt.Printf("expected:\n%s", stale.Want) } return err } ``` A plugin repository keeps its manifest current with one test: ```go func TestPluginManifest(t *testing.T) { kiosk.CheckT(t, ".") } ``` ## API reference | Identifier | Description | |------------|-------------| | `kiosk.FileName` | The manifest file name, `plugin.yaml`. | | `kiosk.SchemaVersion` | The newest schema this package reads, and the one it writes. | | `kiosk.FrameworkModule` | The framework module path; its `go.mod` require is the plugin's framework minimum. | | `kiosk.Header` | The comment line every generated `plugin.yaml` starts with. | | `kiosk.Manifest` | One `plugin.yaml`: `Schema`, `ID`, `Module`, `Package`, `Name`, `Description`, `Framework`, `Requires`, `Overrides`, written in that order. | | `kiosk.Requires` | Required plugin ID to minimum version; marshals sorted by ID. An empty minimum means the plugin ships in the same module or its module is not a `go.mod` require. | | `kiosk.Requires.MarshalYAML` | Emits the map as a mapping sorted by ID. | | `kiosk.Overrides` | The persisted `--name` and `--description` values. | | `kiosk.Marshal` | The `plugin.yaml` bytes: the header line, then the fields. | | `kiosk.Parse` | Reads `plugin.yaml` bytes; unknown fields are ignored, a newer schema is an error. | | `kiosk.DeriveOptions` | Override changes for `kiosk.Derive`: nil keeps the persisted value, a pointer to `""` clears it. | | `kiosk.Derive` | Builds the manifest of a plugin directory from its sources and returns it with warnings. | | `kiosk.Generate` | `kiosk.Derive` plus `kiosk.Marshal`: the bytes `kiosk.Write` would write. | | `kiosk.Write` | Generates and writes `/plugin.yaml` and returns its path. | | `kiosk.Check` | Returns a `kiosk.StaleError` when `/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 | Command | Purpose | |---------|---------| | `summer plugin:manifest [dir]` | Writes `plugin.yaml` for the plugin in `dir` (default: the current directory). `--pretend` prints it instead, `--check` fails when the committed file is missing or stale, `--name` and `--description` set or (with an empty value) clear the overrides. | ## Dependencies - 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`, `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 ```sh go test ./modules/kiosk/... ``` `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.