docs: generic root README
- Describe SummerCMS as a framework: key concepts, requirements, repository layout - Verified quick start on examples/hello with the required config and known issues - Modules table linking all 18 module READMEs with their summary sentences - Module path plus local replace pattern, development commands, design notes
This commit is contained in:
154
README.md
154
README.md
@@ -1,37 +1,151 @@
|
|||||||
# SummerCMS (Go)
|
# SummerCMS (Go)
|
||||||
|
|
||||||
## Framework, not the application
|
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.
|
||||||
|
|
||||||
SummerCMS is the Go framework for the Golem15 stack: compiled plugins, configuration, data primitives, HTTP routing, the admin shell, and parity tooling. The Płytarium application and its binary live in the sibling [`fonoteka.go`](../fonoteka.go) repository, so this checkout is not the application binary.
|
## Key concepts
|
||||||
|
|
||||||
## Two-repository development layout
|
- **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`.
|
||||||
|
|
||||||
This repository has one `go.mod`; framework libraries live under [`modules/`](modules/). During development, `fonoteka.go/go.mod` uses a `replace git.golem15.com/golem15/summercms => ../summercms.go` directive to use this checkout. The same local-replace pattern is shown in [`examples/hello/go.mod`](examples/hello/go.mod), alongside the example's compiled plugin modules.
|
## Requirements
|
||||||
|
|
||||||
## Recreate the Phase 10 admin login
|
- 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.
|
||||||
|
|
||||||
Follow the [Fonoteka database setup](../fonoteka.go/README.md#database-setup) to create a `pl-PL` Postgres database and set `SUMMER_DATABASE__DSN`; the app repository owns its configuration and credentials. From `fonoteka.go`, run:
|
## Quick start
|
||||||
|
|
||||||
```bash
|
`examples/hello` is a small application with three compiled plugins. Install the `summer` CLI from the repository root, then build the example:
|
||||||
summer build
|
|
||||||
./bin/fonoteka migrate
|
```sh
|
||||||
./bin/fonoteka serve
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
The admin SPA is embedded with `embed.FS` in that Fonoteka binary. Its configured backend URI is `/plytadmin`, so open the running app at that path; do not use `summer serve` from this framework repository to open the admin.
|
`greeter:hello` prints the example's layered configuration and needs no database. `key:generate` also runs without a database.
|
||||||
|
|
||||||
## Honest cutover status
|
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)):
|
||||||
|
|
||||||
A working local admin login is not a DNS flip of `plytarium.com`. The production flip is Phase 15, after jobs and search in Phase 11 and the remaining API routes in Phases 12–14. Until then, directing production to this binary would take the public Nuxt application and MCP service offline. The operator procedure remains in the PHP `docs/deploy/plytarium.com.md`; this is not a production deployment runbook.
|
1. Create a PostgreSQL 16 database with the locale lagoon checks for:
|
||||||
|
|
||||||
## Framework modules
|
```sql
|
||||||
|
CREATE DATABASE hello TEMPLATE template0 ENCODING 'UTF8' LOCALE_PROVIDER icu ICU_LOCALE 'pl-PL';
|
||||||
|
```
|
||||||
|
|
||||||
Each framework library has its own short onboarding note:
|
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`:
|
||||||
|
|
||||||
- [`backpack`](modules/backpack/README.md), [`boardwalk`](modules/boardwalk/README.md), [`bonfire`](modules/bonfire/README.md), [`bouncer`](modules/bouncer/README.md), [`cabana`](modules/cabana/README.md), [`compass`](modules/compass/README.md)
|
```yaml
|
||||||
- [`festival`](modules/festival/README.md), [`fetchguard`](modules/fetchguard/README.md), [`lagoon`](modules/lagoon/README.md), [`pact`](modules/pact/README.md), [`party`](modules/party/README.md), [`phrasebook`](modules/phrasebook/README.md)
|
body_limits:
|
||||||
- [`postcard`](modules/postcard/README.md), [`surf`](modules/surf/README.md), [`tide`](modules/tide/README.md), [`towel`](modules/towel/README.md), [`wire`](modules/wire/README.md), [`wristband`](modules/wristband/README.md)
|
default_bytes: 1048576
|
||||||
|
upload_bytes: 10485760
|
||||||
|
```
|
||||||
|
|
||||||
## Why Go and the v1 target
|
3. Point the binary at the database, give it an application key and an uploads bucket, then migrate and serve:
|
||||||
|
|
||||||
SummerCMS replaces the earlier Scala attempt with a deliberately conventional Go stack; see [why Go, not Scala](.planning/notes/why-go-not-scala.md). Its v1 target is Płytarium: the existing Nuxt app and MCP server must run unchanged against the Go backend. See [the v1 target note](.planning/notes/v1-target-plytarium.md).
|
```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. |
|
||||||
|
| [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. |
|
||||||
|
| [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)
|
||||||
|
|||||||
Reference in New Issue
Block a user