feat(bonfire): global flags, Artisan list and exit codes

- Global options -v/-vv/-vvv, -q, --ansi/--no-ansi, -n and --env on every
  command; PeekGlobals reads them before config loads, OnGlobals hooks
  re-apply them after parsing (diary.ApplyGlobals in the generated main)
- Bare binary, --help and list print the Artisan list with the version
  header from BuildVersion and namespace blocks
- Execute runs the root through fang (no version, man page or completions)
  and returns 0, 1 or 2; Fail renders errors before the root exists
- NewRoot rejects duplicate command names (ErrDuplicateCommand) and flags
  that reuse a global option (ErrReservedFlag)
- Generated main and summer use PeekGlobals, compass.Open with --env,
  diary.FromGlobals and bonfire.Execute; docs checker knows the globals
This commit is contained in:
Jakub Zych
2026-10-21 12:13:23 +02:00
parent b7c07591cb
commit 8d4d7ce7a7
25 changed files with 1158 additions and 154 deletions

View File

@@ -10,7 +10,7 @@ Application logging modelled on Laravel's logging config: named channels (consol
`diary` owns the process logger of a SummerCMS application. `config/log.yaml` names a default channel, a global level and a map of channels, the way Laravel's `config/logging.php` does: a console channel that prints styled lines on a terminal, daily files with retention, and a stack that fans a record out to several channels. Code logs through named component loggers (`diary.For`), so every record carries the plugin or module it came from. Every channel is wrapped by `sunscreen.Wrap`, so credentials are redacted whichever channel a record reaches.
The application `main` that `summer build` generates calls `diary.Bootstrap` as its first statement, which installs a console channel before configuration is read, and `diary.Setup` right after `compass` loads `config/`.
The application `main` that `summer build` generates reads the console global options first (`bonfire.PeekGlobals`), calls `diary.Bootstrap` with them, which installs a console channel before configuration is read, and `diary.Setup` right after `compass` loads `config/`. Once the command line is parsed, `diary.ApplyGlobals` re-applies `-v`, `-q` and `--ansi`/`--no-ansi`.
## Role in the framework
@@ -33,6 +33,7 @@ Use cases:
- Daily files: the path `storage/logs/summer.log` gives files `storage/logs/summer-2026-10-21.log`, created mode 0640 in directories created 0750, opened on the first record, rotated at local midnight, and deleted after the retention in days (only files of that name pattern). A relative path resolves against the application root. A directory that cannot be written logs one warning from component `diary` on the console and disables the channel; it never fails the start-up.
- Redaction: every channel leaf is wrapped by `sunscreen.Wrap`.
- Runtime verbosity: `diary.SetVerbosity` lets info (1) or debug (2) through for every component and adds source locations on styled lines (3); quiet raises only the console channels to warn.
- Console global options: `diary.FromGlobals` turns `-v`/`-vv`/`-vvv`, `-q` and `--ansi`/`--no-ansi` into `diary.Options`, and `diary.ApplyGlobals` applies them to the running setup (the generated main registers it with `bonfire.OnGlobals`).
- The process default logger (`slog.Default`) and the standard `log` package log through the configured channels without a component.
## Usage
@@ -58,16 +59,20 @@ and writes warnings and errors as JSON to `storage/logs/summer-YYYY-MM-DD.log`.
The generated `main` installs it:
```go
func run(args []string, out io.Writer) error {
diary.Bootstrap(os.Stderr, diary.Options{})
cfg, err := compass.Load("config")
func run(args []string, out io.Writer) int {
g := bonfire.PeekGlobals(args)
diary.Bootstrap(os.Stderr, diary.FromGlobals(g))
cfg, err := compass.Open(compass.Options{Dir: "config", Env: g.Env})
if err != nil {
return err
return bonfire.Fail(os.Stderr, "acme", err, g)
}
if err := diary.Setup(cfg, diary.Options{}); err != nil {
return err
if err := diary.Setup(cfg, diary.FromGlobals(g)); err != nil {
return bonfire.Fail(os.Stderr, "acme", err, g)
}
// ...
root, err := bonfire.NewRoot("acme", commands, out,
bonfire.WithVersion(bonfire.BuildVersion()), bonfire.OnGlobals(diary.ApplyGlobals))
// ...
}
```
@@ -81,6 +86,8 @@ Tests that set diary up restore the previous process logger with `t.Cleanup(diar
| `diary.Bootstrap` | Installs a console channel on a writer before configuration is read and makes diary the process default logger. |
| `diary.Setup` | Parses `log.*` from a `compass` configuration, builds the channels and installs them; returns configuration errors. |
| `diary.SetVerbosity` | Changes verbosity and quiet at runtime. |
| `diary.FromGlobals` | Returns the `diary.Options` (verbosity, quiet, color mode) of the console global options in a `bonfire.Globals`. |
| `diary.ApplyGlobals` | Applies parsed `bonfire.Globals` to the running setup: verbosity and quiet, and the console color mode when it changed. |
| `diary.Verbosity` | Returns the current verbosity. |
| `diary.Reset` | Drops the setup, closes its files and restores the process default logger captured at the first setup. |
| `diary.Close` | Closes the open daily files; later records open them again. |