refactor(cmd): use factories instead of global cobra command structs (#238)

This enables us to create new commands for e2e tests.
This commit is contained in:
Julian Tölle 2025-08-24 16:44:05 +02:00 committed by GitHub
parent 5b5b29c0b5
commit e6503da93a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 128 additions and 111 deletions

View file

@ -7,21 +7,23 @@ import (
"os/signal" "os/signal"
"runtime/debug" "runtime/debug"
"syscall" "syscall"
"time"
"github.com/lmittmann/tint"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var logger *slog.Logger func NewRootCmd() *cobra.Command {
var cmd = &cobra.Command{
Use: "rp",
Short: "",
Long: ``,
Version: version(),
SilenceUsage: true, // Makes it harder to find the actual error
SilenceErrors: true, // We log manually with slog
}
var rootCmd = &cobra.Command{ cmd.AddCommand(newRunCommand())
Use: "rp",
Short: "", return cmd
Long: ``,
Version: version(),
SilenceUsage: true, // Makes it harder to find the actual error
SilenceErrors: true, // We log manually with slog
} }
func version() string { func version() string {
@ -66,24 +68,13 @@ func Execute() {
// Make sure to stop listening on signals after receiving the first signal to hand control of the signal back // Make sure to stop listening on signals after receiving the first signal to hand control of the signal back
// to the runtime. The Go runtime implements a "force shutdown" if the signal is received again. // to the runtime. The Go runtime implements a "force shutdown" if the signal is received again.
<-ctx.Done() <-ctx.Done()
logger.InfoContext(ctx, "Received shutdown signal, stopping...") slog.InfoContext(ctx, "Received shutdown signal, stopping...")
stop() stop()
}() }()
err := rootCmd.ExecuteContext(ctx) err := NewRootCmd().ExecuteContext(ctx)
if err != nil { if err != nil {
logger.ErrorContext(ctx, err.Error()) slog.ErrorContext(ctx, err.Error())
os.Exit(1) os.Exit(1)
} }
} }
func init() {
logger = slog.New(
tint.NewHandler(os.Stderr, &tint.Options{
Level: slog.LevelDebug,
TimeFormat: time.RFC3339,
}),
)
slog.SetDefault(logger)
}

View file

@ -2,6 +2,7 @@ package cmd
import ( import (
"fmt" "fmt"
"log/slog"
"slices" "slices"
"strings" "strings"
@ -12,103 +13,104 @@ import (
"github.com/apricote/releaser-pleaser/internal/forge" "github.com/apricote/releaser-pleaser/internal/forge"
"github.com/apricote/releaser-pleaser/internal/forge/github" "github.com/apricote/releaser-pleaser/internal/forge/github"
"github.com/apricote/releaser-pleaser/internal/forge/gitlab" "github.com/apricote/releaser-pleaser/internal/forge/gitlab"
"github.com/apricote/releaser-pleaser/internal/log"
"github.com/apricote/releaser-pleaser/internal/updater" "github.com/apricote/releaser-pleaser/internal/updater"
"github.com/apricote/releaser-pleaser/internal/versioning" "github.com/apricote/releaser-pleaser/internal/versioning"
) )
var runCmd = &cobra.Command{ func newRunCommand() *cobra.Command {
Use: "run", var (
RunE: run, flagForge string
} flagBranch string
flagOwner string
var ( flagRepo string
flagForge string flagExtraFiles string
flagBranch string flagUpdaters []string
flagOwner string
flagRepo string
flagExtraFiles string
flagUpdaters []string
)
func init() {
rootCmd.AddCommand(runCmd)
runCmd.PersistentFlags().StringVar(&flagForge, "forge", "", "")
runCmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "")
runCmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "")
runCmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "")
runCmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "")
runCmd.PersistentFlags().StringSliceVar(&flagUpdaters, "updaters", []string{}, "")
}
func run(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
var err error
logger.DebugContext(ctx, "run called",
"forge", flagForge,
"branch", flagBranch,
"owner", flagOwner,
"repo", flagRepo,
) )
var f forge.Forge var cmd = &cobra.Command{
Use: "run",
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
logger := log.GetLogger(cmd.ErrOrStderr())
forgeOptions := forge.Options{ var err error
Repository: flagRepo,
BaseBranch: flagBranch, logger.DebugContext(ctx, "run called",
"forge", flagForge,
"branch", flagBranch,
"owner", flagOwner,
"repo", flagRepo,
)
var f forge.Forge
forgeOptions := forge.Options{
Repository: flagRepo,
BaseBranch: flagBranch,
}
switch flagForge {
case "gitlab":
logger.DebugContext(ctx, "using forge GitLab")
f, err = gitlab.New(logger, &gitlab.Options{
Options: forgeOptions,
Path: fmt.Sprintf("%s/%s", flagOwner, flagRepo),
})
if err != nil {
slog.ErrorContext(ctx, "failed to create client", "err", err)
return fmt.Errorf("failed to create gitlab client: %w", err)
}
case "github":
logger.DebugContext(ctx, "using forge GitHub")
f = github.New(logger, &github.Options{
Options: forgeOptions,
Owner: flagOwner,
Repo: flagRepo,
})
default:
return fmt.Errorf("unknown --forge: %s", flagForge)
}
extraFiles := parseExtraFiles(flagExtraFiles)
updaterNames := parseUpdaters(flagUpdaters)
updaters := []updater.Updater{}
for _, name := range updaterNames {
switch name {
case "generic":
updaters = append(updaters, updater.Generic(extraFiles))
case "changelog":
updaters = append(updaters, updater.Changelog())
case "packagejson":
updaters = append(updaters, updater.PackageJson())
default:
return fmt.Errorf("unknown updater: %s", name)
}
}
releaserPleaser := rp.New(
f,
logger,
flagBranch,
conventionalcommits.NewParser(logger),
versioning.SemVer,
extraFiles,
updaters,
)
return releaserPleaser.Run(ctx)
},
} }
switch flagForge { cmd.PersistentFlags().StringVar(&flagForge, "forge", "", "")
case "gitlab": cmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "")
logger.DebugContext(ctx, "using forge GitLab") cmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "")
f, err = gitlab.New(logger, &gitlab.Options{ cmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "")
Options: forgeOptions, cmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "")
Path: fmt.Sprintf("%s/%s", flagOwner, flagRepo), cmd.PersistentFlags().StringSliceVar(&flagUpdaters, "updaters", []string{}, "")
})
if err != nil {
logger.ErrorContext(ctx, "failed to create client", "err", err)
return fmt.Errorf("failed to create gitlab client: %w", err)
}
case "github":
logger.DebugContext(ctx, "using forge GitHub")
f = github.New(logger, &github.Options{
Options: forgeOptions,
Owner: flagOwner,
Repo: flagRepo,
})
default:
return fmt.Errorf("unknown --forge: %s", flagForge)
}
extraFiles := parseExtraFiles(flagExtraFiles) return cmd
updaterNames := parseUpdaters(flagUpdaters)
updaters := []updater.Updater{}
for _, name := range updaterNames {
switch name {
case "generic":
updaters = append(updaters, updater.Generic(extraFiles))
case "changelog":
updaters = append(updaters, updater.Changelog())
case "packagejson":
updaters = append(updaters, updater.PackageJson())
default:
return fmt.Errorf("unknown updater: %s", name)
}
}
releaserPleaser := rp.New(
f,
logger,
flagBranch,
conventionalcommits.NewParser(logger),
versioning.SemVer,
extraFiles,
updaters,
)
return releaserPleaser.Run(ctx)
} }
func parseExtraFiles(input string) []string { func parseExtraFiles(input string) []string {

View file

@ -2,6 +2,7 @@ package main
import ( import (
"github.com/apricote/releaser-pleaser/cmd/rp/cmd" "github.com/apricote/releaser-pleaser/cmd/rp/cmd"
_ "github.com/apricote/releaser-pleaser/internal/log"
) )
func main() { func main() {

23
internal/log/log.go Normal file
View file

@ -0,0 +1,23 @@
package log
import (
"io"
"log/slog"
"os"
"time"
"github.com/lmittmann/tint"
)
func GetLogger(w io.Writer) *slog.Logger {
return slog.New(
tint.NewHandler(w, &tint.Options{
Level: slog.LevelDebug,
TimeFormat: time.RFC3339,
}),
)
}
func init() {
slog.SetDefault(GetLogger(os.Stderr))
}