From e6503da93a115cd4a8f8b242cab19fce8c206880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Sun, 24 Aug 2025 16:44:05 +0200 Subject: [PATCH] refactor(cmd): use factories instead of global cobra command structs (#238) This enables us to create new commands for e2e tests. --- cmd/rp/cmd/root.go | 39 ++++------ cmd/rp/cmd/run.go | 176 ++++++++++++++++++++++---------------------- cmd/rp/main.go | 1 + internal/log/log.go | 23 ++++++ 4 files changed, 128 insertions(+), 111 deletions(-) create mode 100644 internal/log/log.go diff --git a/cmd/rp/cmd/root.go b/cmd/rp/cmd/root.go index 2799e6d..f2dd180 100644 --- a/cmd/rp/cmd/root.go +++ b/cmd/rp/cmd/root.go @@ -7,21 +7,23 @@ import ( "os/signal" "runtime/debug" "syscall" - "time" - "github.com/lmittmann/tint" "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{ - Use: "rp", - Short: "", - Long: ``, - Version: version(), - SilenceUsage: true, // Makes it harder to find the actual error - SilenceErrors: true, // We log manually with slog + cmd.AddCommand(newRunCommand()) + + return cmd } 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 // to the runtime. The Go runtime implements a "force shutdown" if the signal is received again. <-ctx.Done() - logger.InfoContext(ctx, "Received shutdown signal, stopping...") + slog.InfoContext(ctx, "Received shutdown signal, stopping...") stop() }() - err := rootCmd.ExecuteContext(ctx) + err := NewRootCmd().ExecuteContext(ctx) if err != nil { - logger.ErrorContext(ctx, err.Error()) + slog.ErrorContext(ctx, err.Error()) os.Exit(1) } } - -func init() { - logger = slog.New( - tint.NewHandler(os.Stderr, &tint.Options{ - Level: slog.LevelDebug, - TimeFormat: time.RFC3339, - }), - ) - - slog.SetDefault(logger) -} diff --git a/cmd/rp/cmd/run.go b/cmd/rp/cmd/run.go index a70e915..d6bbe4b 100644 --- a/cmd/rp/cmd/run.go +++ b/cmd/rp/cmd/run.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "log/slog" "slices" "strings" @@ -12,103 +13,104 @@ import ( "github.com/apricote/releaser-pleaser/internal/forge" "github.com/apricote/releaser-pleaser/internal/forge/github" "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/versioning" ) -var runCmd = &cobra.Command{ - Use: "run", - RunE: run, -} - -var ( - flagForge string - flagBranch 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, +func newRunCommand() *cobra.Command { + var ( + flagForge string + flagBranch string + flagOwner string + flagRepo string + flagExtraFiles string + flagUpdaters []string ) - 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{ - Repository: flagRepo, - BaseBranch: flagBranch, + var err error + + 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 { - 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 { - 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) - } + cmd.PersistentFlags().StringVar(&flagForge, "forge", "", "") + cmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "") + cmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "") + cmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "") + cmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "") + cmd.PersistentFlags().StringSliceVar(&flagUpdaters, "updaters", []string{}, "") - 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) + return cmd } func parseExtraFiles(input string) []string { diff --git a/cmd/rp/main.go b/cmd/rp/main.go index 462629b..734709c 100644 --- a/cmd/rp/main.go +++ b/cmd/rp/main.go @@ -2,6 +2,7 @@ package main import ( "github.com/apricote/releaser-pleaser/cmd/rp/cmd" + _ "github.com/apricote/releaser-pleaser/internal/log" ) func main() { diff --git a/internal/log/log.go b/internal/log/log.go new file mode 100644 index 0000000..32219fd --- /dev/null +++ b/internal/log/log.go @@ -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)) +}