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:
50
docs/console/introduction.md
Normal file
50
docs/console/introduction.md
Normal 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`.
|
||||
67
docs/console/scaffolding.md
Normal file
67
docs/console/scaffolding.md
Normal 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.
|
||||
99
docs/console/setup-and-maintenance.md
Normal file
99
docs/console/setup-and-maintenance.md
Normal 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
47
docs/console/utilities.md
Normal 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.
|
||||
96
docs/console/writing-commands.md
Normal file
96
docs/console/writing-commands.md
Normal 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`.
|
||||
Reference in New Issue
Block a user