# 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 creates, 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 15 or newer for any application that uses the data layer ([lagoon](modules/lagoon/README.md)). - Node.js 22.6 or newer, only when working on the admin SPA in `admin/`. - Docker, to run the PostgreSQL 16 database of an application created by `summer new` (its `compose.yaml`), and for the integration tests that start PostgreSQL or Mailpit containers through testcontainers-go. ## Quick start Create an application with one command. The script checks for Go 1.27 and Docker, clones the framework at its latest release tag into `./summercms`, installs the `summer` CLI and runs `summer new demo`: ```sh curl -fsSL https://summercms.io/new | bash -s -- demo cd sm-demo-app docker compose up -d --wait # PostgreSQL 16 on 127.0.0.1:54320 summer build summer migrate summer serve # then: curl http://127.0.0.1:8080/ ``` The [sunrise](modules/sunrise/README.md) module holds the script and documents its `SUMMERCMS_REPO`, `SUMMERCMS_DIR` and `SUMMERCMS_REF` overrides. Later, `curl -fsSL https://summercms.io/update | bash` run inside the application moves the framework checkout forward, reinstalls summer and rebuilds that application. With a framework checkout already in place, install the CLI from it and run `summer new` in the directory next to the checkout; it finds a sibling `summercms` or `summercms.go` checkout, or takes `--framework `: ```sh go -C summercms install ./cmd/summer summer new demo ``` ### The example application `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 database on PostgreSQL 15 or newer: ```sql CREATE DATABASE hello; ``` 2. 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`, `summer serve`, `summer queue:work`, `summer queue:clear`, `summer schedule:run`, `summer route:list`, `summer key:generate`, `summer admin:create` and `summer admin:reset-password`, run from the application directory, delegate to the built binary in `bin/` and pass their flags to it unchanged. ## 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`). | | `docs/` | Documentation source; `summer docs:build` renders it into a static site. | | `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 | Name mnemonic | |--------|---------|---------------| | [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. | The one bag you pack for the beach; it carries everything the application needs: configuration, services, events and 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. | A beachcomber searches the shore for finds; this module keeps the search index in step so content can be found. | | [boardwalk](modules/boardwalk/README.md) | HTTP handler that serves the embedded admin SPA build under a configurable path prefix. | The walkway along the beach front; it is the path admins take into the admin SPA. | | [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. | The evening fire where everyone gathers; the command line is where you sit down and fire off commands. | | [bouncer](modules/bouncer/README.md) | Authentication for SummerCMS: HS256 JWT minting, verification and refresh, request guards, a revoked-token blacklist and bcrypt password helpers. | Checks IDs at the beach club door; it authenticates requests and turns away the ones without a valid token. | | [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. | The beach hut where the staff run the beach; it is the admin backend. | | [compass](modules/compass/README.md) | Layered YAML configuration with per-environment directories, `SUMMER_` environment overrides, embedded plugin defaults and dot-path access. | Points you the right way; configuration tells the application how to behave in each environment. | | [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. | A line of dancers moving one after another; jobs queue up and are worked through in order. | | [festival](modules/festival/README.md) | Typed, synchronous event bus with listener priorities, payload collection and stop-when-handled dispatch. | A summer festival announces events and the crowd reacts; it is the event bus. | | [fetchguard](modules/fetchguard/README.md) | Guarded outbound HTTP client and fetcher that blocks private and reserved addresses, enforces host, size and timeout limits, and offers an explicit trusted mode for operator-configured endpoints. | A lifeguard for outbound fetches; it watches every request the application sends out and stops the dangerous ones. | | [flare](modules/flare/README.md) | Web Push delivery with VAPID (RFC 8292) and aes128gcm payload encryption (RFC 8291) behind a small Pusher interface. | A signal flare reaches people far away; it sends Web Push notifications to browsers. | | [lagoon](modules/lagoon/README.md) | Postgres data layer: the shared GORM connection, per-plugin migrations, model helpers and file attachments. | Calm, deep water where everything settles; it is the database layer where data is stored. | | [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. | Beams a signal out to everyone watching; it broadcasts realtime messages to subscribers. | | [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. | An agreement both sides keep; it holds the interfaces a plugin agrees to implement. | | [party](modules/party/README.md) | Compiled plugin registry that orders plugins by their dependencies and runs their Register and Boot lifecycle. | The guest list and the order people arrive in; it registers plugins and boots them in dependency order. | | [phrasebook](modules/phrasebook/README.md) | Namespaced translation catalogs loaded from plugin YAML, with locale fallback, placeholder interpolation and CLDR pluralization. | What you pack to speak the local language on holiday; it holds the translations. | | [postcard](modules/postcard/README.md) | Transactional mail from plugin-owned Markdown templates and layouts, delivered through a memory, log or SMTP driver. | What you send home in summer; it delivers the application's mail. | | [sunrise](modules/sunrise/README.md) | Bootstrap script that installs the summer CLI and creates a new SummerCMS application, or updates an existing one, in one command, embedded so it is versioned with the framework. | The start of a summer day; it starts a new application, or updates one, in one command. | | [sunscreen](modules/sunscreen/README.md) | Credential-redacting slog handler that keeps API keys, tokens and passwords out of application logs. | Protects you from exposure; it keeps secrets out of the logs. | | [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. | Catching the incoming waves; it routes incoming HTTP requests to their handlers. | | [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. | The tide goes out and comes back, and you compare the waterline; it records traffic from one backend, replays it against another and compares. | | [towel](modules/towel/README.md) | Request-scoped actor, organization, collection and locale values carried through `context.Context`. | You carry it everywhere and it marks your spot; it carries the actor, organization and locale through each request. | | [wire](modules/wire/README.md) | JSON response helpers and value types that keep API bodies byte-compatible with a PHP (WinterCMS/Laravel) backend. | What goes over the wire; it keeps response bodies byte-for-byte the same as the PHP backend's. | | [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. | The festival wristband that lets you in for the event; it issues OAuth access for MCP clients. | ## 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. `summer new` writes this `go.mod` for you, with a relative `replace` to the framework checkout and a committed `go.work` (see [Go modules and workspaces](docs/architecture/go-modules-and-workspaces.md)). Written by hand, 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. `summer docs:build` renders `docs/` and every module README into a static site under `site/` (use `--out` for another directory, `--check` to validate without writing). Code blocks with a `src=` reference are copies of real source, and `src=` works on top-level code blocks of `docs/` pages only; after changing that source, run `summer docs:sync` to refresh the copies. `summer docs:serve` builds the same site into a temporary directory and previews it at `http://127.0.0.1:8088` (`--addr` to change it), rebuilding when the docs, a module or a `src=` source changes; a failed rebuild prints its problems and keeps serving the last good build. It listens only on a loopback address unless you pass `--allow-remote`. Search needs this server: browsers block the search index over `file://`. ## 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)