feat(bonfire): Artisan help, examples, JSON listing and error hints

- help <command> and <command> --help print Description, Usage,
  Arguments, Options with defaults, Global options, Help and Examples
- Command.Examples and Command.Hidden (appended, additive); list --all
  shows hidden commands, list --format=json prints a bonfire.Listing
- CheckDocs reports commands lacking description, long help, examples or
  argument and option descriptions
- Error block with the ERROR badge, WithHint explanation and Try lines
  ({bin} replaced), cobra suggestions as Try, -v error chain, messages
  scrubbed by sunscreen; Reported errors print nothing and exit 1
This commit is contained in:
Jakub Zych
2026-10-21 12:21:24 +02:00
parent 80fae5c279
commit fb32886775
12 changed files with 1130 additions and 52 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"strings"
"git.golem15.com/golem15/summercms/modules/bonfire"
)
@@ -51,3 +52,111 @@ func ExampleCatalog() {
}
// Output: publish hello-world, tags [news go], dry run true
}
func ExampleExample() {
export := bonfire.Command{
Name: "blog:export",
Description: "Export published posts as JSON",
Long: "Writes every published post, newest first, to a JSON file.",
Flags: []bonfire.Flag{
{Name: "path", Description: "Output file", Default: "posts.json"},
},
Examples: []bonfire.Example{
{Command: "blog:export --path=backup.json", Description: "Export to another file"},
},
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
return nil
},
}
// Hidden commands run, but list shows them only with --all.
cleanup := bonfire.Command{
Name: "blog:cleanup",
Description: "Remove orphaned uploads",
Hidden: true,
}
root, err := bonfire.NewRootIO("acme", []bonfire.Command{export, cleanup}, strings.NewReader(""), os.Stdout, os.Stdout)
if err != nil {
fmt.Println(err)
return
}
bonfire.Execute(context.Background(), root, []string{"help", "blog:export"})
// Output:
// Description:
// Export published posts as JSON
//
// Usage:
// blog:export [options]
//
// Options:
// --path=PATH Output file [default: "posts.json"]
//
// Global options:
// -h, --help Display help for the given command
// -q, --quiet Do not output any message
// -v|vv|vvv Increase verbosity
// --ansi|no-ansi Force (or disable) ANSI output
// -n, --no-interaction Do not ask any interactive question
// --env The environment the command runs under
//
// Help:
// Writes every published post, newest first, to a JSON file.
//
// Examples:
// # Export to another file
// bin/acme blog:export --path=backup.json
}
func ExampleWithHint() {
publish := bonfire.Command{
Name: "blog:publish",
Description: "Publish a draft post",
Args: []bonfire.Arg{{Name: "slug", Description: "Post slug", Required: true}},
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
slug, _ := in.Argument("slug")
return bonfire.WithHint(fmt.Errorf("blog: no draft named %q", slug),
"Only drafts can be published; published posts keep their slug.",
"{bin} blog:drafts")
},
}
root, err := bonfire.NewRootIO("acme", []bonfire.Command{publish}, strings.NewReader(""), os.Stdout, os.Stdout)
if err != nil {
fmt.Println(err)
return
}
code := bonfire.Execute(context.Background(), root, []string{"blog:publish", "hello-world"})
fmt.Println("exit code:", code)
// Output:
// ERROR blog: no draft named "hello-world"
//
// Only drafts can be published; published posts keep their slug.
//
// Try:
// bin/acme blog:drafts
//
// Run with -v for details.
//
// exit code: 1
}
func ExampleCheckDocs() {
commands := []bonfire.Command{
{
Name: "blog:publish",
Description: "Publish a draft post",
Long: "Publishes the draft with the given slug and notifies subscribers.",
Args: []bonfire.Arg{{Name: "slug", Description: "Post slug", Required: true}},
Examples: []bonfire.Example{{Command: "blog:publish hello-world"}},
},
{
Name: "blog:reindex",
Description: "Rebuild the search index",
Flags: []bonfire.Flag{{Name: "batch", Default: "500"}},
},
}
for _, problem := range bonfire.CheckDocs(commands) {
fmt.Println(problem)
}
// Output: blog:reindex: missing long help, examples, option --batch description
}