feat(11.1-03): add the Setup and Console docs sections

- setup: introduction, installation rewritten from install to serve,
  configuration with the keys an application sets
- console: introduction, setup and maintenance, scaffolding, writing
  commands, utilities; every command name is checker-verified
- bonfire ExampleCatalog shows arguments, bare and repeatable flags
- index links the section introductions; TestDocsRequiredPages lists
  the seven new pages
This commit is contained in:
Jakub Zych
2026-09-30 22:16:36 +02:00
parent 1f8f5e1b51
commit a896f3ff81
12 changed files with 580 additions and 2 deletions

View File

@@ -0,0 +1,50 @@
---
title: Console introduction
description: The two command-line programs of SummerCMS, the summer developer tool and the application binary, and how summer delegates runtime commands.
section: console
order: 10
---
# Console introduction
WinterCMS has one console entry point, `php artisan`. SummerCMS has two programs, because the developer tooling and the running application are separate binaries:
| Program | Where it comes from | What it does |
|---------|---------------------|--------------|
| `summer` | Installed once from the framework with `go install ./cmd/summer`. | Builds and watches applications, scaffolds plugins and their parts, records API parity fixtures and builds these docs. |
| The application binary, for example `./bin/acme` | Written by `summer build` into the application's `bin/` directory. | Runs the application: the HTTP server, migrations, workers, the scheduler, admin accounts and every command its plugins add. |
The application binary is what you deploy, so everything that must run in production, such as migrations and workers, is a command of the binary rather than of `summer`.
## Getting help
Both programs list their commands with `--help`, and every command accepts `--help` for its arguments and flags:
```sh
summer --help
summer make:model --help
./bin/acme --help
./bin/acme migrate:rollback --help
```
## Commands that summer delegates
During development you often work from the application directory with `summer` alone. These `summer` commands find the application's `summer.yaml`, build `bin/<binary>` when it does not exist yet, and run the same command of the binary with the same arguments:
| summer command | Runs |
|----------------|------|
| `summer migrate` | `./bin/acme migrate` |
| `summer migrate:rollback` | `./bin/acme migrate:rollback` |
| `summer migrate:status` | `./bin/acme migrate:status` |
| `summer serve` | `./bin/acme serve` |
| `summer queue:work` | `./bin/acme queue:work` |
| `summer queue:clear` | `./bin/acme queue:clear` |
| `summer schedule:run` | `./bin/acme schedule:run` |
`summer` does not rebuild an existing binary before it delegates. Run `summer build` after you change code, or keep `summer dev` running. Every other runtime command, such as `route:list`, `key:generate` or `admin:create`, is run on the binary directly.
## The sections of this chapter
- [Setup and maintenance](setup-and-maintenance.md) lists every command of the application binary.
- [Scaffolding](scaffolding.md) covers `summer build`, `summer dev` and the `make:` commands.
- [Writing commands](writing-commands.md) shows how a plugin adds its own commands.
- [Utilities](utilities.md) covers the parity and documentation commands of `summer`.

View File

@@ -0,0 +1,67 @@
---
title: Scaffolding
description: Build and watch an application, and generate plugins, models, migrations, console commands, jobs and admin controllers with the summer make commands.
section: console
order: 30
---
# Scaffolding
The `summer` tool builds applications and generates the files a plugin is made of, the way `create:plugin`, `create:model` and the other `create:` commands do in WinterCMS. Every generated file compiles as written, so you can build straight after running a command.
## Building and watching
| Command | Purpose |
|---------|---------|
| `summer build` | Reads `summer.yaml`, generates `plugins.gen.go` and `main.go`, and builds the binary into `bin/`. Run it from the application directory or any directory below it. |
| `summer dev` | Builds the application, starts the binary, and rebuilds and restarts it whenever a Go or YAML source, `go.mod`, `go.work`, `.env` or `summer.yaml` changes. |
```sh
summer build
summer dev
```
Do not edit `main.go` or `plugins.gen.go`: `summer build` rewrites them from the manifest.
## Creating a plugin
`summer make:plugin` takes a plugin ID in `vendor.plugin` form and creates the plugin module in `plugins/<plugin>` of the current application, with the directory layout described in [Plugin registration](../plugins/registration.md):
```sh
summer make:plugin acme.blog
summer plugin:add plugins/blog
```
`summer plugin:add` then registers the local module: it adds the plugin to `summer.yaml`, adds a `require` and a local `replace` to the application's `go.mod`, and adds the directory to `go.work`. The next `summer build` compiles the plugin in.
## Generating plugin parts
The other `make:` commands add one artifact to an existing plugin. Each takes the plugin ID and an exported Go name:
```sh
summer make:model acme.blog Post
summer make:migration acme.blog AddPublishedAt
summer make:command acme.blog Publish
summer make:job acme.blog ImportPosts
summer make:admin-controller acme.blog Posts
```
When you run a command inside a plugin directory, leave the ID out and pass only the name. The tool finds the plugin from the nearest `plugin.go` above the current directory:
```sh
cd plugins/blog
summer make:model Comment
```
| Command | Writes | Notes |
|---------|--------|-------|
| `summer make:model` | `models/<name>.go` and `updates/<timestamp>_create_<table>.go` | The table name is the plugin ID and the plural name in snake case, such as `acme_blog_posts`. `--no-migration` skips the migration. |
| `summer make:migration` | `updates/<timestamp>_<name>.go` | An empty gormigrate migration with up and down steps to fill in. |
| `summer make:command` | `console/<name>.go` | A `bonfire.Command` named `<plugin>:<name>`, such as `blog:publish`. |
| `summer make:job` | `jobs/<name>.go` | A typed job built with `conga.Job`; the plugin never imports the queue library. |
| `summer make:admin-controller` | `controllers/<name>.go`, `controllers/<name>/config_form.yaml`, `controllers/<name>/config_list.yaml`, `models/<name>/fields.yaml`, `models/<name>/columns.yaml` | A `pact.AdminController` with WinterCMS-shaped form and list configuration. |
File names are the snake-case form of the name: `AddPublishedAt` becomes `add_published_at`. Migration file names start with a 14-digit timestamp, so they sort in the order you created them; migrations created in the same second get consecutive timestamps.
After writing the files, every `make:` command regenerates the plugin's `registry.gen.go`, which lists the plugin's models, migrations, commands, jobs and admin controllers, and runs `go mod tidy` in the plugin. The capability methods of a scaffolded `plugin.go` return those generated lists. If your `plugin.go` was written by hand and does not call them, the command prints a note naming the accessors to add.
A command refuses to overwrite an existing file or to declare a name the package already has.

View File

@@ -0,0 +1,99 @@
---
title: Setup and maintenance
description: Every runtime command of an application binary, with its flags and purpose, from migrations and the server to workers, admin accounts and realtime checks.
section: console
order: 20
---
# Setup and maintenance
Every application binary carries the framework's runtime commands. The examples use a binary named `acme`; yours is named by `binary:` in `summer.yaml`.
## Migrations
| Command | Flags | Purpose |
|---------|-------|---------|
| `migrate` | none | Runs the framework migrations, then every plugin's migrations in dependency order. |
| `migrate:rollback` | `--plugin <id>` | Rolls back the last migration of the plugin; without the flag, of the last activated plugin that has migrations. |
| `migrate:status` | none | Prints a table of plugin, history table and applied migration IDs. |
```sh
./bin/acme migrate
./bin/acme migrate:status
./bin/acme migrate:rollback --plugin acme.blog
```
Each plugin keeps its own migration history table, so rolling back one plugin never touches another. The migrations are listed in [lagoon](../../modules/lagoon/README.md).
## Application key
| Command | Flags | Purpose |
|---------|-------|---------|
| `key:generate` | none | Prints a fresh base64 32-byte key for `app.key`. It writes nothing and needs no database. |
```sh
./bin/acme key:generate
```
Copy the printed value into `SUMMER_APP__KEY`. When you rotate the key, keep the old one in `app.previous_keys` so existing encrypted columns can still be read.
## The HTTP server
| Command | Flags | Purpose |
|---------|-------|---------|
| `serve` | `--addr` (default `:8080`) | Opens the database and uploads bucket, builds the router, starts the in-process job worker and serves HTTP until SIGINT or SIGTERM, then shuts down within 10 seconds. |
| `route:list` | none | Builds the router the way `serve` does, without opening the database or listening, and prints every route with its method, pattern, plugin, middleware and raw flag. |
```sh
./bin/acme route:list
./bin/acme serve --addr 127.0.0.1:8080
```
The default `--addr` listens on every interface. Pass a loopback address during development, and put a reverse proxy in front of the binary in production. The routing options are in [surf](../../modules/surf/README.md).
## Admin accounts
| Command | Arguments and flags | Purpose |
|---------|---------------------|---------|
| `admin:create` | `--email`, `--password` (both required), `--login`, `--role <code>`, `--superuser` | Creates an activated backend administrator. `--login` defaults to the lower-cased email. |
| `admin:reset-password` | `<identifier>` (login or email), `--password` | Sets a new password and revokes every token issued before the reset. |
```sh
./bin/acme admin:create --email admin@example.com --password '<secret>' --superuser
./bin/acme admin:reset-password admin@example.com --password '<secret>'
```
Passwords passed as flags end up in your shell history. Prefer reading them from a secrets manager into a variable. The admin is described in [cabana](../../modules/cabana/README.md).
## Queues and the scheduler
| Command | Arguments and flags | Purpose |
|---------|---------------------|---------|
| `queue:work` | `--queue <name>`, repeatable | Runs a job worker in the foreground on the named queues (default: every known queue) until SIGINT or SIGTERM. An unknown queue is an error that lists the known ones. |
| `queue:clear` | `[queue]` (default `default`) | Deletes the waiting, scheduled and retryable jobs of one queue and prints how many it cleared. Running jobs are never touched. |
| `schedule:run` | `--once` | Without `--once`, runs a scheduler-only worker until stopped. With `--once`, runs the entries due in the current minute and exits, for system cron. |
```sh
./bin/acme queue:work --queue default --queue imports
./bin/acme queue:clear imports
./bin/acme schedule:run --once
```
`serve` runs a job worker in the same process unless `queue.work_in_serve` is `false`; set it to `false` when you run `queue:work` separately. [Task scheduling](../plugins/scheduling.md) explains the scheduler, and [conga](../../modules/conga/README.md) the queue settings.
## Realtime and push
These commands are not added by the generated `main`. An application that uses Centrifugo or Web Push appends them to the list one of its plugins returns from `pact.HasCommands`: `centrifugo.Commands` returns `websockets:health`, and `flare.Commands` returns the two push commands.
| Command | Arguments and flags | Purpose |
|---------|---------------------|---------|
| `websockets:health` | none | Calls the Centrifugo `info` API and prints the configuration. Exits 1 when the API key is missing or the call fails. The key itself is never printed. |
| `websockets:generate-vapid-keys` | `--update`, `--show-current` | Shows the configured VAPID keys, truncated, then generates a new pair. With `--update` it saves them to the environment's `overrides.yaml`; without it, it prints the variables to set by hand. |
| `websockets:test-push` | `<user_id>`, `--show-config` | Lists a user's push subscriptions and, after confirmation, sends one encrypted test notification to each. |
```sh
./bin/acme websockets:health
./bin/acme websockets:generate-vapid-keys --show-current
./bin/acme websockets:test-push 1
```
The settings are in [lighthouse](../../modules/lighthouse/README.md) and [flare](../../modules/flare/README.md).

47
docs/console/utilities.md Normal file
View File

@@ -0,0 +1,47 @@
---
title: Utilities
description: The summer commands for API parity testing and for building, syncing and previewing this documentation, with their flags.
section: console
order: 50
---
# Utilities
Besides building and scaffolding, the `summer` tool carries two groups of utility commands: API parity testing, used when you port an existing backend, and the documentation build.
## API parity commands
When you port an existing backend to SummerCMS, its real responses are the contract your port must meet. The parity commands wrap [tide](../../modules/tide/README.md): they record fixtures from the reference backend and replay them against the port. Every address they listen on or connect to must be a loopback address, and captured secrets go to a variables file with mode 0600, outside the committed fixtures.
| Command | Flags | Purpose |
|---------|-------|---------|
| `summer parity:proxy` | `--listen` (default `127.0.0.1:8422`), `--upstream` (default `http://127.0.0.1:8423`), `--session`, `--rules`, `--vars`, `--fixtures`, `--update` | Runs a recording reverse proxy in front of the reference backend. Point a real client at it; each named session (from the `X-Parity-Session` header, or `--session`) is written as one fixture. |
| `summer parity:record` | `--spec`, `--target`, `--output`, `--rules`, `--vars`, `--update`; `--manifest`, `--fixtures`, `--next-batch`, `--resume`, `--allow-incomplete`, `--require-recorded` | Sends the requests of a YAML spec to a target and records the responses as a fixture, or records the missing cases of a route manifest in batches of at most 15. |
| `summer parity:replay` | `--fixtures`, `--target`, `--vars`, `--manifest`, `--self-check`, `--require-recorded` | Replays recorded fixtures against a backend and reports the differences after masking IDs and timestamps. |
| `summer parity:broadcasts` | `--flow`, `--target`, `--vars`, `--listen` (default `127.0.0.1:8424`), `--out`, `--name`, `--step`, `--ids`, `--rules`, `--api-key`, `--settle` (default `500ms`), `--pending` | Runs a flow against the reference backend with a fake Centrifugo server and records the realtime publications it sends into a golden file. |
A typical port records once against the reference backend and replays against the Go backend on every change:
```sh
summer parity:record --spec testdata/parity/posts.spec.yaml --target http://127.0.0.1:8000 --output testdata/parity/posts.yaml --vars /tmp/parity/vars.yaml
summer parity:replay --fixtures testdata/parity --target http://127.0.0.1:8080 --vars /tmp/parity/vars.yaml
```
## Documentation commands
These docs are Markdown files under `docs/`, plus every module README, built into a static site by `summer`. Run the commands from the framework root.
| Command | Flags | Purpose |
|---------|-------|---------|
| `summer docs:build` | `--root` (default `.`), `--src`, `--out`, `--base-url`, `--check` | Checks every page and writes the site to `site/` (or `--out`): HTML pages, a raw `.md` copy of each page, `llms.txt`, `llms-full.txt` and the search index. With `--check` it only reports problems and writes nothing. |
| `summer docs:sync` | `--root` (default `.`), `--src` | Rewrites every code block that has a `src=` reference from its source file. |
| `summer docs:serve` | `--root` (default `.`), `--src`, `--base-url`, `--addr` (default `127.0.0.1:8088`), `--allow-remote` | Builds the site into a temporary directory, serves it and rebuilds when a page, a module or a referenced source changes. A failed rebuild prints its problems and keeps serving the last good build. |
```sh
summer docs:build --check
summer docs:sync
summer docs:serve
```
`docs:build` fails, and writes nothing, when a page names an identifier that does not exist, links to a missing page or anchor, shows a command that neither `summer` nor an application binary has, or has a `src=` code block that differs from its source. After you change code that a page shows, run `summer docs:sync` to refresh the copies.
`docs:serve` listens only on a loopback address unless you pass `--allow-remote`. Use it to preview search, which browsers block when you open the built files directly from disk.

View File

@@ -0,0 +1,96 @@
---
title: Writing commands
description: Add console commands to a plugin with bonfire.Command values, arguments, flags, styled output and prompts, and run commands in-process.
section: console
order: 40
---
# Writing commands
A plugin adds console commands to the application binary the way a WinterCMS plugin calls `registerConsoleCommand`. In SummerCMS a command is a plain `bonfire.Command` value, and the plugin returns its commands from `pact.HasCommands`. `summer make:command acme.blog Publish` generates a starting point in `console/publish.go`.
## Defining a command
A `bonfire.Command` has a name, a description, its positional arguments and flags, and a run function:
- The name is in `namespace:verb` form, such as `blog:publish`. Plugin commands must use this form; only a few framework commands have bare names.
- Each `bonfire.Arg` is a positional argument with a name, a description and a `Required` marker. The usage line shows required arguments as `<name>` and optional ones as `[name]`.
- Each `bonfire.Flag` is a string flag. Set `bonfire.Flag.Bare` for a switch such as `--dry-run` that stores `true` when given alone, and `bonfire.Flag.Repeatable` for a flag that can be given several times.
- `bonfire.Command.Run` receives the context, a `bonfire.Input` and a `bonfire.Output`.
Read arguments with `bonfire.Input.Argument`, scalar and bare flags with `bonfire.Input.Flag`, and repeatable flags with `bonfire.Input.Flags`, which returns the values in the order given:
```go src=modules/bonfire/example_test.go#ExampleCatalog
publish := bonfire.Command{
Name: "blog:publish",
Description: "Publish a post",
Args: []bonfire.Arg{{Name: "slug", Description: "Post slug", Required: true}},
Flags: []bonfire.Flag{
{Name: "dry-run", Description: "Report without writing", Bare: true},
{Name: "tag", Description: "Tag to add (repeatable)", Repeatable: true},
},
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
slug, _ := in.Argument("slug")
dryRun, _ := in.Flag("dry-run")
out.Printf("publish %s, tags %v, dry run %s\n", slug, in.Flags("tag"), dryRun)
return nil
},
}
// The generated main publishes a catalog of every command on the app.
catalog := bonfire.NewCatalog([]bonfire.Command{publish})
args := []string{"hello-world", "--tag", "news", "--tag", "go", "--dry-run"}
if err := catalog.Call(context.Background(), "blog:publish", args, os.Stdout); err != nil {
fmt.Println(err)
}
// Output: publish hello-world, tags [news go], dry run true
```
## Registering commands
Return the commands from the plugin's `Commands` method, which implements `pact.HasCommands`. The generated `main` appends every plugin's commands after the framework's runtime commands. Command names are not checked for duplicates, so keep your commands in your plugin's own namespace, such as `blog:`.
A scaffolded plugin's `Commands` method returns the generated list of everything in `console/`, so commands created with `summer make:command` are registered without editing `plugin.go`.
## Output
`bonfire.Output` is the console your command writes to. Besides `bonfire.Output.Printf` and `bonfire.Output.Println`, it provides:
- status lines: `bonfire.Output.Info`, `bonfire.Output.Success`, `bonfire.Output.Warning` and `bonfire.Output.Error` (the last one writes to the error stream);
- widgets: `bonfire.Output.Table`, `bonfire.Output.Spinner` around a function and `bonfire.Output.Progress` for a progress bar, which fall back to plain lines when the output is not a terminal;
- prompts: `bonfire.Output.Ask`, `bonfire.Output.Confirm`, `bonfire.Output.Choice` and `bonfire.Output.Secret`. Prompts return their defaults when input ends, so a command run from cron or a script never hangs.
Return an error from `Run` to fail the command. The binary prints it and exits with status 1.
## Calling commands in-process
`bonfire.Call` runs one command of a slice by name with its arguments and writes the output to any writer, like `Artisan::call` in Laravel. Tests use it to exercise a command without building a binary:
```go src=modules/bonfire/example_test.go#ExampleCall
commands := []bonfire.Command{{
Name: "acme:greet",
Description: "Greet someone by name",
Args: []bonfire.Arg{{Name: "name", Required: true}},
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
name, _ := in.Argument("name")
out.Printf("Hello, %s\n", name)
return nil
},
}}
if err := bonfire.Call(context.Background(), commands, "acme:greet", []string{"blog"}, os.Stdout); err != nil {
fmt.Println(err)
}
// Output: Hello, blog
```
The generated `main` also publishes the application's complete command list as a `bonfire.Catalog` on the container. Code outside the command line, such as the scheduler, looks it up and calls commands through `bonfire.Catalog.Call`, and checks for one with `bonfire.Catalog.Has`.
## Running your command
After `summer build`, your command is part of the binary:
```sh
./bin/hello greeter:hello
```
That command comes from the greeter plugin of `examples/hello`, whose `Commands` method returns one `bonfire.Command`.