- lighthouse: Service/From with realtime.driver selection, RegisterDriver registry, null/log/memory drivers, Route/Surface/Mount, users and actors - centrifugo: HTTP API client (apikey header, 2xx success, no request without a key), five-generator HS256 TokenIssuer, TokenHandler with the WinterCMS 401/503 bodies - module README and root modules table row
154 lines
11 KiB
Markdown
154 lines
11 KiB
Markdown
# 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:<secret>@127.0.0.1:5432/hello?sslmode=disable'
|
|
export SUMMER_APP__KEY='<value printed by key:generate>'
|
|
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. |
|
|
| [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. |
|
|
| [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/<name>`. 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)
|