Jakub Zych a955d2a3d4 fix(16.3): refresh CSRF coverage golden and blog scaffold layout
Account token POST/DELETE routes made TestPhase10Coverage expect 32 unsafe
routes; token_scopes.go is hand-authored and excluded from the scaffold pin.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-11 13:53:31 +02:00
2026-10-11 13:37:30 +02:00
2026-10-06 22:32:36 +02:00
2026-10-09 10:35:16 +02:00

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. 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 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 compiles those definitions at boot and serves them as a JSON admin API, and boardwalk serves the embedded Vue admin SPA that renders them.
  • Headless API. Plugins declare HTTP routes and middleware. surf assembles them onto a standard library net/http ServeMux, and wire 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).
  • 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:

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 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 <path>:

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:

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):

  1. Create a database on PostgreSQL 15 or newer:

    CREATE DATABASE hello;
    
  2. Point the binary at the database, give it an application key and an uploads bucket, then migrate and serve:

    ./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, 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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/<name>. 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). 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 and 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:

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:

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/, including:

Description
Content management framework for Go,
https://summercms.io
Readme 23 MiB
Languages
Go 68.6%
TypeScript 14%
Shell 7.7%
Vue 6.3%
JavaScript 1.7%
Other 1.7%