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

@@ -136,13 +136,15 @@ func docsOptions(in bonfire.Input) docsite.Options {
}
// docsCommands collects the command names docs pages may show: the summer
// tool's own commands, and every command the generated application main
// tool's own commands, list and help (added by bonfire.NewRoot), and every
// command the generated application main
// registers (TestDocsCommandsMirrorGeneratedMain keeps this list in step
// with internal/build) plus the realtime and push commands applications
// append. The constructors only capture the app, so an empty config is
// enough; nothing runs.
func docsCommands() *docsite.Commands {
var tool []string
// list and help come from bonfire.NewRoot, in both programs.
tool := []string{"list", "help"}
for _, c := range toolCommands() {
tool = append(tool, c.Name)
}
@@ -156,7 +158,7 @@ func docsCommands() *docsite.Commands {
appCmds = append(appCmds, cabana.RuntimeCommands(app)...)
appCmds = append(appCmds, centrifugo.Commands(app)...)
appCmds = append(appCmds, flare.Commands(app)...)
names := make([]string, 0, len(appCmds))
names := []string{"list", "help"}
for _, c := range appCmds {
names = append(names, c.Name)
}

View File

@@ -401,12 +401,12 @@ func first(lines []string) string {
func TestDocsCommandNames(t *testing.T) {
cmds := docsCommands()
for _, want := range []string{"docs:build", "make:plugin", "migrate:status"} {
for _, want := range []string{"docs:build", "make:plugin", "migrate:status", "list", "help"} {
if !slices.Contains(cmds.Tool, want) {
t.Errorf("Tool is missing %s: %v", want, cmds.Tool)
}
}
for _, want := range []string{"key:generate", "route:list", "admin:create", "queue:clear", "websockets:health"} {
for _, want := range []string{"key:generate", "route:list", "admin:create", "queue:clear", "websockets:health", "list", "help"} {
if !slices.Contains(cmds.App, want) {
t.Errorf("App is missing %s: %v", want, cmds.App)
}

View File

@@ -9,20 +9,20 @@ import (
"git.golem15.com/golem15/summercms/internal/build"
"git.golem15.com/golem15/summercms/internal/dev"
"git.golem15.com/golem15/summercms/modules/bonfire"
"git.golem15.com/golem15/summercms/modules/diary"
"git.golem15.com/golem15/summercms/modules/lagoon"
)
func main() {
root, err := bonfire.NewRoot("summer", toolCommands(), os.Stdout)
args := os.Args[1:]
g := bonfire.PeekGlobals(args)
diary.Bootstrap(os.Stderr, diary.FromGlobals(g))
root, err := bonfire.NewRoot("summer", toolCommands(), os.Stdout,
bonfire.WithVersion(bonfire.BuildVersion()), bonfire.OnGlobals(diary.ApplyGlobals))
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
root.SetArgs(os.Args[1:])
if err := root.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
os.Exit(bonfire.Fail(os.Stderr, "summer", err, g))
}
os.Exit(bonfire.Execute(context.Background(), root, args))
}
func toolCommands() []bonfire.Command {