# SummerCMS (Go) SummerCMS is a content management framework for Go, inspired by WinterCMS. An application built on it compiles into a single binary: the framework modules, the application's plugins, the embedded admin SPA and the console commands all ship as one executable. ## Key concepts - **Compiled plugins.** A plugin is a Go module that implements the capability interfaces in [pact](modules/pact/README.md). The `summer build` command reads the application's `summer.yaml` manifest, generates the plugin import list and builds the binary. Plugins are registered at build time and never loaded at runtime. [party](modules/party/README.md) orders them by dependency and runs their register and boot lifecycle. - **YAML-driven admin.** Plugins describe admin lists, forms, filters and relations in WinterCMS-style YAML (`columns.yaml`, `fields.yaml`). [cabana](modules/cabana/README.md) compiles those definitions at boot and serves them as a JSON admin API, and [boardwalk](modules/boardwalk/README.md) serves the embedded Vue admin SPA that renders them. - **Headless API.** Plugins declare HTTP routes and middleware. [surf](modules/surf/README.md) assembles them onto a standard library `net/http` ServeMux, and [wire](modules/wire/README.md) keeps JSON responses byte-compatible with a PHP (WinterCMS/Laravel) backend. - **Scaffolding CLI.** The `summer` tool builds and watches applications and scaffolds plugins, models, migrations, console commands, jobs and admin controllers. Every application binary also carries runtime commands such as `migrate`, `serve` and `route:list`. ## Requirements - Go 1.27. - PostgreSQL 16 for any application that uses the data layer ([lagoon](modules/lagoon/README.md)). lagoon currently requires the database's default locale to be ICU `pl-PL`; see [Known issues](#known-issues). - Node.js 22.6 or newer, only when working on the admin SPA in `admin/`. - Docker, only for the integration tests that start PostgreSQL or Mailpit containers through testcontainers-go. ## Quick start `examples/hello` is a small application with three compiled plugins. Install the `summer` CLI from the repository root, then build the example: ```sh go install ./cmd/summer cd examples/hello summer build # generates plugins.gen.go and main.go, writes bin/hello ./bin/hello --help ./bin/hello greeter:hello ``` `greeter:hello` prints the example's layered configuration and needs no database. `key:generate` also runs without a database. The `migrate`, `migrate:status`, `migrate:rollback`, `serve` and admin commands open PostgreSQL, so they need more configuration. Configuration comes from YAML files in `config/`, and any key can be overridden with a `SUMMER_`-prefixed environment variable in which a double underscore separates path segments (see [compass](modules/compass/README.md)): 1. Create a PostgreSQL 16 database with the locale lagoon checks for: ```sql CREATE DATABASE hello TEMPLATE template0 ENCODING 'UTF8' LOCALE_PROVIDER icu ICU_LOCALE 'pl-PL'; ``` 2. Add the request body limits that `serve` and `route:list` require. The example does not ship them yet, and surf needs numeric values, which the string-valued environment overlay cannot supply. Create `config/http.yaml` in `examples/hello`: ```yaml body_limits: default_bytes: 1048576 upload_bytes: 10485760 ``` 3. Point the binary at the database, give it an application key and an uploads bucket, then migrate and serve: ```sh ./bin/hello key:generate # prints a base64 key; copy the value export SUMMER_DATABASE__DSN='postgres://acme:@127.0.0.1:5432/hello?sslmode=disable' export SUMMER_APP__KEY='' export SUMMER_STORAGE__UPLOADS__BUCKET_URL='mem://' ./bin/hello migrate ./bin/hello route:list ./bin/hello serve --addr 127.0.0.1:8080 curl http://127.0.0.1:8080/items/1 ``` `summer migrate`, `summer migrate:status`, `summer migrate:rollback` and `summer serve`, run from the application directory, delegate to the built binary in `bin/`. ### Known issues - `examples/hello` has no `http.body_limits` configuration, so `serve` and `route:list` fail with `surf: config http.body_limits.default_bytes is required` until you add it as in step 2. The same gap makes the example's `TestTypedItemRoute` fail. - lagoon refuses any database whose default locale is not ICU `pl-PL`. - The committed `examples/hello/main.go` is older than what `summer build` generates now, so building the example (or running its tests, which build it) leaves that file modified. Restore it with `git checkout -- examples/hello/main.go` if you do not intend to commit it. ## Repository layout | Path | Contents | |------|----------| | `admin/` | The Vue 3 and TypeScript admin SPA; its build output is embedded by boardwalk. | | `cmd/` | The `summer` CLI (`cmd/summer`). | | `examples/` | Example applications; `examples/hello` is the reference application and workspace. | | `internal/` | CLI internals: the build and scaffolding generator, the watch loop and an OpenAPI conversion tool. | | `modules/` | The framework modules, one Go package each, listed below. | | `scripts/` | Verification scripts used as phase gates. | ## Modules | Module | Purpose | |--------|---------| | [backpack](modules/backpack/README.md) | Per-instance application container that holds the configuration, a typed service registry, the event bus and the set of activated plugins. | | [beachcomber](modules/beachcomber/README.md) | Search index sync for GORM models: after-commit upserts and deletes through a pluggable engine, gated by an application kill-switch. | | [boardwalk](modules/boardwalk/README.md) | HTTP handler that serves the embedded admin SPA build under a configurable path prefix. | | [bonfire](modules/bonfire/README.md) | Declarative console commands for the `summer` tool and application binaries, adapted to Cobra with typed input, prompts and styled output. | | [bouncer](modules/bouncer/README.md) | Authentication for SummerCMS: HS256 JWT minting, verification and refresh, request guards, a revoked-token blacklist and bcrypt password helpers. | | [cabana](modules/cabana/README.md) | Schema-driven admin backend that compiles WinterCMS-style YAML list, form, filter and relation definitions at boot and serves them as a JSON admin API next to the embedded admin SPA. | | [compass](modules/compass/README.md) | Layered YAML configuration with per-environment directories, `SUMMER_` environment overrides, embedded plugin defaults and dot-path access. | | [conga](modules/conga/README.md) | Background jobs on River over the shared Postgres pool: transactional dispatch, a `summer_jobs` progress record, in-process or dedicated workers, and a wall-clock scheduler. | | [festival](modules/festival/README.md) | Typed, synchronous event bus with listener priorities, payload collection and stop-when-handled dispatch. | | [fetchguard](modules/fetchguard/README.md) | Guarded outbound HTTPS fetcher that blocks private and reserved addresses and enforces host, size and timeout limits. | | [flare](modules/flare/README.md) | Web Push delivery with VAPID (RFC 8292) and aes128gcm payload encryption (RFC 8291) behind a small Pusher interface. | | [lagoon](modules/lagoon/README.md) | Postgres data layer: the shared GORM connection, per-plugin migrations, model helpers and file attachments. | | [lighthouse](modules/lighthouse/README.md) | Transport-neutral realtime: a publisher interface with pluggable drivers, subscribe-time channel authorization, and model broadcasts enqueued in the write transaction. | | [pact](modules/pact/README.md) | Capability interfaces that compiled plugins implement to contribute routes, config, migrations, middleware, commands, admin screens, translations, mail templates and jobs. | | [party](modules/party/README.md) | Compiled plugin registry that orders plugins by their dependencies and runs their Register and Boot lifecycle. | | [phrasebook](modules/phrasebook/README.md) | Namespaced translation catalogs loaded from plugin YAML, with locale fallback, placeholder interpolation and CLDR pluralization. | | [postcard](modules/postcard/README.md) | Transactional mail from plugin-owned Markdown templates and layouts, delivered through a memory, log or SMTP driver. | | [surf](modules/surf/README.md) | HTTP routing for SummerCMS: collects plugin routes and named middleware into a `net/http` ServeMux with constraints, rate limiting, body limits, CORS and panic recovery, and provides the `serve` and `route:list` commands. | | [tide](modules/tide/README.md) | HTTP parity toolkit that records request and response fixtures from a reference backend, replays them against a new one and reports normalized differences. | | [towel](modules/towel/README.md) | Request-scoped actor, organization, collection and locale values carried through `context.Context`. | | [wire](modules/wire/README.md) | JSON response helpers and value types that keep API bodies byte-compatible with a PHP (WinterCMS/Laravel) backend. | | [wristband](modules/wristband/README.md) | An OAuth authorization server for MCP clients: RFC 8414 metadata, RFC 7591 dynamic client registration, the authorization-code flow with PKCE, consent operations and refresh-token rotation over application-supplied storage. | ## Using SummerCMS in an application The framework is a single Go module, `git.golem15.com/golem15/summercms`; each framework package is imported from `git.golem15.com/golem15/summercms/modules/`. An application requires that module and, during development, points it at a local checkout with a `replace` directive. For an application module `acme` in a sibling directory: ``` 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 ``` The application lists its plugins in `summer.yaml` (module path, binary name, plugin ids and module paths); `summer plugin:add` registers a local plugin module in that manifest and in the workspace, and `summer make:plugin` scaffolds a new one. [`examples/hello/go.mod`](examples/hello/go.mod) and [`examples/hello/summer.yaml`](examples/hello/summer.yaml) are the working reference: the example replaces the framework with `../..` and its plugin modules with local paths. ## Development Run these from the repository root: ```sh go vet ./... go test ./... # full suite; the Docker-backed integration tests start containers go test -short ./... # fast loop without Docker; skips the testcontainers-backed tests ``` `go test ./...` at the root does not include `examples/hello`, which is a separate workspace module; run its tests with `cd examples/hello && go test ./...`. The admin SPA lives in `admin/`. Install its dependencies with `npm --prefix admin ci`, then use its scripts: ```sh npm --prefix admin run dev # Vite dev server npm --prefix admin run build # type-check, then build into modules/boardwalk/dist npm --prefix admin run typecheck # vue-tsc only npm --prefix admin run test # Vitest npm --prefix admin run gen:api # regenerate TypeScript types from admin/openapi/admin.json ``` `summer dev` watches an application's sources and rebuilds its binary on change. ## Design notes Decisions and background live in [`.planning/notes/`](.planning/notes/), including: - [Why Go, not Scala](.planning/notes/why-go-not-scala.md) - [Apparatus dissolved into the framework](.planning/notes/apparatus-dissolved-into-framework.md) - [Plugin file layout: WinterCMS directories as Go subpackages](.planning/notes/plugin-layout-winter-directories.md) - [A direct OAuth 2.1-style port: wristband](.planning/notes/oauth2-wristband-direct-port.md)