Files
summercms/modules/bonfire/execute.go
Jakub Zych 9d06a78f62 feat: Artisan checklist for application commands, hints and coverage gate
- migrate, migrate:rollback and deferred:purge gain --pretend and --force;
  they and queue:clear confirm only in production on an interactive
  terminal, so deploys over SSH proceed as before
- result lines: Applied N migrations, Rolled back <id> of <plugin>,
  Cleared N jobs from <queue>
- --json for migrate:status, key:generate, route:list, admin:token:create,
  admin:token:list, admin:token:scopes and websockets:health; -q keeps the
  key and the token
- hints for an empty DSN, an unreachable database, an invalid app.key, a
  missing uploads bucket and an application binary run below its root
- flare and websockets:health return bonfire.Reported after printing
- long help and examples for every application command, greeter:hello and
  blog:publish; cmd/summer TestCommandsDocumented fails on any gap
2026-10-21 13:16:21 +02:00

103 lines
2.7 KiB
Go

package bonfire
import (
"context"
"io"
"os"
"slices"
"strings"
"charm.land/fang/v2"
"github.com/spf13/cobra"
)
// Exit codes returned by Execute and Fail.
const (
// ExitOK means the command succeeded.
ExitOK = 0
// ExitFailure means the command failed.
ExitFailure = 1
// ExitUsage means the command was called wrongly: an unknown command or
// flag, or a wrong number of arguments.
ExitUsage = 2
)
// Execute runs root with args and returns the process exit code: ExitOK,
// ExitFailure, or ExitUsage for usage errors. With no command word in args
// (only global options, -h or --help) it prints the command list. Errors are
// rendered on the root's error stream by RenderError through fang's error
// handler; fang is used without its version flag, man page and completion
// commands.
func Execute(ctx context.Context, root *cobra.Command, args []string) int {
if ctx == nil {
ctx = context.Background()
}
if args == nil {
// cobra reads os.Args when the args are nil.
args = []string{}
}
g := PeekGlobals(args)
if !hasCommandWord(args) {
args = slices.DeleteFunc(slices.Clone(args), func(a string) bool {
return a == "-h" || a == "--help"
})
}
resetFlags(root)
root.SetArgs(args)
name := root.Name()
err := fang.Execute(ctx, root,
fang.WithoutVersion(),
fang.WithoutManpage(),
fang.WithoutCompletions(),
fang.WithErrorHandler(func(w io.Writer, _ fang.Styles, err error) {
// fang hands over a colorprofile writer built from the
// environment only; RenderError unwraps it and applies
// --ansi and --no-ansi itself.
RenderError(w, name, wrongDirectory(name, err, args, ""), g)
}),
)
return exitCode(err)
}
// Fail renders err like Execute does and returns ExitFailure (ExitOK for a
// nil error). The generated main uses it for errors that happen before the
// command root exists, such as a configuration that does not load. Like
// Execute, it adds the wrong-directory hint for an application binary run
// outside its application root.
func Fail(w io.Writer, name string, err error, g Globals) int {
if err == nil {
return ExitOK
}
RenderError(w, name, wrongDirectory(name, err, os.Args[1:], ""), g)
return ExitFailure
}
func exitCode(err error) int {
switch {
case err == nil:
return ExitOK
case isUsage(err):
return ExitUsage
default:
return ExitFailure
}
}
// hasCommandWord reports whether args, before "--", hold a token that is
// neither a flag nor the value of --env.
func hasCommandWord(args []string) bool {
for i := 0; i < len(args); i++ {
a := args[i]
switch {
case a == "--":
return false
case a == "--"+flagEnv:
i++
case strings.HasPrefix(a, "-"):
default:
return true
}
}
return false
}