Files
summercms/docs/console/writing-commands.md
Jakub Zych 14f6982c4d feat(bonfire): add Passthrough and Long command fields
- Command.Passthrough turns off cobra flag parsing and uses arbitrary
  args, so every argument after the command name, including --help, -h
  and --, reaches Run through Input.Args unchanged
- Command.Long sets cobra's Long text for the help command; without it
  the description is shown as before
- both fields are additive: commands without Passthrough parse flags and
  reject unknown ones exactly as before
- README and docs/console/writing-commands.md describe both fields
2026-10-08 15:49:39 +02:00

98 lines
5.5 KiB
Markdown

---
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`.
- `bonfire.Command.Long` is optional extended help shown by the help command; without it the description is shown. Set `bonfire.Command.Passthrough` for a command that forwards to another program: flag parsing is turned off, so every argument after the command name, including `--help`, `-h` and `--`, reaches `bonfire.Input.Args` unchanged. Declared flags then appear in the usage text only, and declared arguments are not count-checked.
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`.