diff --git a/cmd/internal/browse.go b/cmd/internal/browse.go index 4d074147..27ebc982 100644 --- a/cmd/internal/browse.go +++ b/cmd/internal/browse.go @@ -7,12 +7,12 @@ import ( "github.com/spf13/cobra" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/cache" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/io" execIO "github.com/flowexec/flow/internal/io/executable" "github.com/flowexec/flow/internal/io/library" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/context" + flowErrors "github.com/flowexec/flow/pkg/errors" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/common" "github.com/flowexec/flow/types/executable" ) @@ -137,7 +137,7 @@ func executableLibrary(ctx *context.Context, cmd *cobra.Command, _ []string) { Substring: subStr, Visibility: visibilityFilter, }, - io.Theme(ctx.Config.Theme.String()), + logger.Theme(ctx.Config.Theme.String()), runFunc, ) SetView(ctx, cmd, libraryModel) @@ -218,7 +218,7 @@ func viewExecutable(ctx *context.Context, cmd *cobra.Command, args []string) { ref := executable.NewRef(execID, verb) exec, err := ctx.ExecutableCache.GetExecutableByRef(ref) - if err != nil && errors.Is(cache.NewExecutableNotFoundError(ref.String()), err) { + if err != nil && errors.Is(flowErrors.NewExecutableNotFoundError(ref.String()), err) { logger.Log().Debugf("Executable %s not found in cache, syncing cache", ref) if err := ctx.ExecutableCache.Update(); err != nil { logger.Log().FatalErr(err) diff --git a/cmd/internal/cache.go b/cmd/internal/cache.go index ea82caa3..5d73e2f9 100644 --- a/cmd/internal/cache.go +++ b/cmd/internal/cache.go @@ -8,11 +8,10 @@ import ( "github.com/spf13/cobra" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/context" - flowIO "github.com/flowexec/flow/internal/io" cacheIO "github.com/flowexec/flow/internal/io/cache" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/services/store" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" ) func RegisterCacheCmd(ctx *context.Context, rootCmd *cobra.Command) { @@ -55,7 +54,7 @@ func cacheSetFunc(ctx *context.Context, cmd *cobra.Command, args []string) { switch { case len(args) == 1: form, err := views.NewForm( - flowIO.Theme(ctx.Config.Theme.String()), + logger.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ diff --git a/cmd/internal/config.go b/cmd/internal/config.go index a9d280cf..bef43a31 100644 --- a/cmd/internal/config.go +++ b/cmd/internal/config.go @@ -12,11 +12,10 @@ import ( "github.com/spf13/cobra" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/io" configIO "github.com/flowexec/flow/internal/io/config" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/config" ) @@ -44,7 +43,7 @@ func registerConfigResetCmd(ctx *context.Context, configCmd *cobra.Command) { func resetConfigFunc(ctx *context.Context, _ *cobra.Command, _ []string) { form, err := views.NewForm( - io.Theme(ctx.Config.Theme.String()), + logger.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ diff --git a/cmd/internal/exec.go b/cmd/internal/exec.go index 3082f14f..383bdbd3 100644 --- a/cmd/internal/exec.go +++ b/cmd/internal/exec.go @@ -14,10 +14,7 @@ import ( "github.com/spf13/cobra" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/cache" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/io" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/runner/exec" @@ -28,6 +25,9 @@ import ( "github.com/flowexec/flow/internal/runner/serial" "github.com/flowexec/flow/internal/services/store" "github.com/flowexec/flow/internal/utils/env" + "github.com/flowexec/flow/pkg/context" + flowErrors "github.com/flowexec/flow/pkg/errors" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" ) @@ -109,7 +109,7 @@ func execFunc(ctx *context.Context, cmd *cobra.Command, verb executable.Verb, ar } e, err := ctx.ExecutableCache.GetExecutableByRef(ref) - if err != nil && errors.Is(cache.NewExecutableNotFoundError(ref.String()), err) { + if err != nil && errors.Is(flowErrors.NewExecutableNotFoundError(ref.String()), err) { logger.Log().Debugf("Executable %s not found in cache, syncing cache", ref) if err := ctx.ExecutableCache.Update(); err != nil { logger.Log().FatalErr(err) @@ -160,7 +160,7 @@ func execFunc(ctx *context.Context, cmd *cobra.Command, verb executable.Verb, ar // add values from the prompt param type to the env map textInputs := pendingFormFields(ctx, e, envMap) if len(textInputs) > 0 { - form, err := views.NewForm(io.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), textInputs...) + form, err := views.NewForm(logger.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), textInputs...) if err != nil { logger.Log().FatalErr(err) } diff --git a/cmd/internal/flags/helpers.go b/cmd/internal/flags/helpers.go index 0e3f4f94..ac5e03fc 100644 --- a/cmd/internal/flags/helpers.go +++ b/cmd/internal/flags/helpers.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" ) //nolint:errcheck diff --git a/cmd/internal/helpers.go b/cmd/internal/helpers.go index c2370f9d..b5763345 100644 --- a/cmd/internal/helpers.go +++ b/cmd/internal/helpers.go @@ -8,10 +8,10 @@ import ( "github.com/spf13/cobra" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" flowIO "github.com/flowexec/flow/internal/io" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" ) @@ -98,7 +98,7 @@ func WaitForTUI(ctx *context.Context, cmd *cobra.Command) { func printContext(ctx *context.Context, cmd *cobra.Command) { if TUIEnabled(ctx, cmd) { - logger.Log().Println(flowIO.Theme(ctx.Config.Theme.String()). + logger.Log().Println(logger.Theme(ctx.Config.Theme.String()). RenderHeader(context.AppName, context.HeaderCtxKey, ctx.String(), 0)) } } diff --git a/cmd/internal/logs.go b/cmd/internal/logs.go index 665bab25..d2e9a005 100644 --- a/cmd/internal/logs.go +++ b/cmd/internal/logs.go @@ -8,10 +8,10 @@ import ( "github.com/spf13/cobra" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" "github.com/flowexec/flow/internal/io/logs" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" ) func RegisterLogsCmd(ctx *context.Context, rootCmd *cobra.Command) { diff --git a/cmd/internal/mcp.go b/cmd/internal/mcp.go index 4668845b..7d85e14c 100644 --- a/cmd/internal/mcp.go +++ b/cmd/internal/mcp.go @@ -3,9 +3,9 @@ package internal import ( "github.com/spf13/cobra" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/mcp" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" ) func RegisterMCPCmd(ctx *context.Context, rootCmd *cobra.Command) { diff --git a/cmd/internal/secret.go b/cmd/internal/secret.go index 1aa53279..974e8d90 100644 --- a/cmd/internal/secret.go +++ b/cmd/internal/secret.go @@ -12,13 +12,12 @@ import ( "github.com/spf13/cobra" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/io" "github.com/flowexec/flow/internal/io/secret" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/utils" envUtils "github.com/flowexec/flow/internal/utils/env" "github.com/flowexec/flow/internal/vault" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/config" ) @@ -50,7 +49,7 @@ func removeSecretFunc(ctx *context.Context, _ *cobra.Command, args []string) { reference := args[0] form, err := views.NewForm( - io.Theme(ctx.Config.Theme.String()), + logger.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ @@ -116,7 +115,7 @@ func setSecretFunc(ctx *context.Context, cmd *cobra.Command, args []string) { value = string(data) case len(args) == 1: form, err := views.NewForm( - io.Theme(ctx.Config.Theme.String()), + logger.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ diff --git a/cmd/internal/sync.go b/cmd/internal/sync.go index 469038ec..d33bbe0d 100644 --- a/cmd/internal/sync.go +++ b/cmd/internal/sync.go @@ -3,9 +3,9 @@ package internal import ( "github.com/spf13/cobra" - "github.com/flowexec/flow/internal/cache" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/cache" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" ) func RegisterSyncCmd(ctx *context.Context, rootCmd *cobra.Command) { diff --git a/cmd/internal/template.go b/cmd/internal/template.go index 024e7895..1fc561be 100644 --- a/cmd/internal/template.go +++ b/cmd/internal/template.go @@ -6,13 +6,13 @@ import ( "github.com/spf13/cobra" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" "github.com/flowexec/flow/internal/io/executable" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/exec" "github.com/flowexec/flow/internal/templates" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" ) func RegisterTemplateCmd(ctx *context.Context, rootCmd *cobra.Command) { diff --git a/cmd/internal/vault.go b/cmd/internal/vault.go index 7a93dea2..9221590a 100644 --- a/cmd/internal/vault.go +++ b/cmd/internal/vault.go @@ -11,13 +11,12 @@ import ( "golang.org/x/exp/maps" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" - flowIO "github.com/flowexec/flow/internal/io" vaultIO "github.com/flowexec/flow/internal/io/vault" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/utils" "github.com/flowexec/flow/internal/vault" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/config" ) @@ -228,7 +227,7 @@ func removeVaultFunc(ctx *context.Context, _ *cobra.Command, args []string) { } form, err := views.NewForm( - flowIO.Theme(ctx.Config.Theme.String()), + logger.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ diff --git a/cmd/internal/workspace.go b/cmd/internal/workspace.go index d741f8c7..74efdb6f 100644 --- a/cmd/internal/workspace.go +++ b/cmd/internal/workspace.go @@ -13,12 +13,11 @@ import ( "golang.org/x/exp/maps" "github.com/flowexec/flow/cmd/internal/flags" - "github.com/flowexec/flow/internal/cache" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/io" workspaceIO "github.com/flowexec/flow/internal/io/workspace" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/cache" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/common" "github.com/flowexec/flow/types/config" "github.com/flowexec/flow/types/workspace" @@ -167,7 +166,7 @@ func removeWorkspaceFunc(ctx *context.Context, _ *cobra.Command, args []string) name := args[0] form, err := views.NewForm( - io.Theme(ctx.Config.Theme.String()), + logger.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ diff --git a/cmd/root.go b/cmd/root.go index d42c183f..4cd616eb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,9 +9,9 @@ import ( "github.com/flowexec/flow/cmd/internal" "github.com/flowexec/flow/cmd/internal/flags" "github.com/flowexec/flow/cmd/internal/version" - "github.com/flowexec/flow/internal/cache" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/cache" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" ) func NewRootCmd(ctx *context.Context) *cobra.Command { @@ -44,6 +44,11 @@ func NewRootCmd(ctx *context.Context) *cobra.Command { } internal.RegisterPersistentFlag(ctx, rootCmd, *flags.LogLevel) internal.RegisterPersistentFlag(ctx, rootCmd, *flags.SyncCacheFlag) + + rootCmd.SetOut(ctx.StdOut()) + rootCmd.SetErr(ctx.StdOut()) + rootCmd.SetIn(ctx.StdIn()) + return rootCmd } @@ -54,11 +59,6 @@ func Execute(ctx *context.Context, rootCmd *cobra.Command) error { panic("root command is not initialized") } - rootCmd.SetOut(ctx.StdOut()) - rootCmd.SetErr(ctx.StdOut()) - rootCmd.SetIn(ctx.StdIn()) - RegisterSubCommands(ctx, rootCmd) - if err := rootCmd.Execute(); err != nil { return fmt.Errorf("failed to execute command: %w", err) } diff --git a/internal/cache/errors.go b/internal/cache/errors.go deleted file mode 100644 index b3732b5d..00000000 --- a/internal/cache/errors.go +++ /dev/null @@ -1,37 +0,0 @@ -package cache - -import ( - "fmt" -) - -type ExecutableNotFoundError struct { - Ref string -} - -func (e ExecutableNotFoundError) Error() string { - return fmt.Sprintf("unable to find executable with reference %s", e.Ref) -} - -func (e ExecutableNotFoundError) Unwrap() error { - return fmt.Errorf("executable not found") -} - -func NewExecutableNotFoundError(ref string) ExecutableNotFoundError { - return ExecutableNotFoundError{Ref: ref} -} - -type CacheUpdateError struct { - Err error -} - -func (e CacheUpdateError) Error() string { - return fmt.Sprintf("unable to update cache - %v", e.Err) -} - -func (e CacheUpdateError) Unwrap() error { - return e.Err -} - -func NewCacheUpdateError(err error) CacheUpdateError { - return CacheUpdateError{Err: err} -} diff --git a/internal/fileparser/fileparser.go b/internal/fileparser/fileparser.go index e1d7c73b..b63b494f 100644 --- a/internal/fileparser/fileparser.go +++ b/internal/fileparser/fileparser.go @@ -6,8 +6,8 @@ import ( "path/filepath" "strings" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/utils" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/fileparser/fileparser_test.go b/internal/fileparser/fileparser_test.go index f21205fc..71b054bf 100644 --- a/internal/fileparser/fileparser_test.go +++ b/internal/fileparser/fileparser_test.go @@ -11,7 +11,7 @@ import ( "go.uber.org/mock/gomock" "github.com/flowexec/flow/internal/fileparser" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/io/cache/output.go b/internal/io/cache/output.go index e84a4d41..21bbf84f 100644 --- a/internal/io/cache/output.go +++ b/internal/io/cache/output.go @@ -2,7 +2,7 @@ package cache import ( "github.com/flowexec/flow/internal/io/common" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" ) func PrintCache(cache map[string]string, format string) { diff --git a/internal/io/common/common.go b/internal/io/common/common.go index 10d0120e..0bbf73d8 100644 --- a/internal/io/common/common.go +++ b/internal/io/common/common.go @@ -6,8 +6,8 @@ import ( "slices" "strings" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/services/open" + "github.com/flowexec/flow/pkg/logger" ) const HeaderContextKey = "ctx" diff --git a/internal/io/config/output.go b/internal/io/config/output.go index 53c31149..1e863472 100644 --- a/internal/io/config/output.go +++ b/internal/io/config/output.go @@ -2,7 +2,7 @@ package config import ( "github.com/flowexec/flow/internal/io/common" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/config" ) diff --git a/internal/io/executable/output.go b/internal/io/executable/output.go index 949f3c39..923868f1 100644 --- a/internal/io/executable/output.go +++ b/internal/io/executable/output.go @@ -2,7 +2,7 @@ package executable import ( "github.com/flowexec/flow/internal/io/common" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/io/executable/views.go b/internal/io/executable/views.go index aa48764a..2acebc41 100644 --- a/internal/io/executable/views.go +++ b/internal/io/executable/views.go @@ -10,9 +10,9 @@ import ( "github.com/flowexec/tuikit/types" "github.com/flowexec/tuikit/views" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/io/common" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/io/library/library.go b/internal/io/library/library.go index c03ce3a7..6a013ea8 100644 --- a/internal/io/library/library.go +++ b/internal/io/library/library.go @@ -9,7 +9,7 @@ import ( "github.com/flowexec/tuikit/themes" "github.com/flowexec/tuikit/views" - "github.com/flowexec/flow/internal/context" + "github.com/flowexec/flow/pkg/context" "github.com/flowexec/flow/types/common" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" diff --git a/internal/io/library/update.go b/internal/io/library/update.go index 7166f97c..3d580c50 100644 --- a/internal/io/library/update.go +++ b/internal/io/library/update.go @@ -11,10 +11,10 @@ import ( "github.com/flowexec/tuikit/io" "github.com/flowexec/tuikit/themes" - "github.com/flowexec/flow/internal/filesystem" "github.com/flowexec/flow/internal/io/common" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/services/open" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" ) var log io.Logger diff --git a/internal/io/library/view.go b/internal/io/library/view.go index 71aca16e..6bd79e0e 100644 --- a/internal/io/library/view.go +++ b/internal/io/library/view.go @@ -10,7 +10,7 @@ import ( "github.com/flowexec/tuikit/themes" "github.com/jahvon/glamour" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/common" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" diff --git a/internal/io/logs/output.go b/internal/io/logs/output.go index 773a1ccd..bb0434ad 100644 --- a/internal/io/logs/output.go +++ b/internal/io/logs/output.go @@ -7,7 +7,7 @@ import ( "gopkg.in/yaml.v3" "github.com/flowexec/flow/internal/io/common" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" ) type entry struct { diff --git a/internal/io/secret/output.go b/internal/io/secret/output.go index e4d7494a..03b6c813 100644 --- a/internal/io/secret/output.go +++ b/internal/io/secret/output.go @@ -3,10 +3,10 @@ package secret import ( "fmt" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/io/common" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/vault" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" ) func PrintSecrets(ctx *context.Context, vaultName string, vlt vault.Vault, format string, plaintext bool) { diff --git a/internal/io/secret/views.go b/internal/io/secret/views.go index 3509efe7..03d9e8ca 100644 --- a/internal/io/secret/views.go +++ b/internal/io/secret/views.go @@ -9,9 +9,9 @@ import ( "github.com/flowexec/tuikit/types" "github.com/flowexec/tuikit/views" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/logger" vault2 "github.com/flowexec/flow/internal/vault" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" ) func NewSecretView( diff --git a/internal/io/vault/output.go b/internal/io/vault/output.go index 68de68a8..d60a1ba8 100644 --- a/internal/io/vault/output.go +++ b/internal/io/vault/output.go @@ -2,7 +2,7 @@ package vault import ( "github.com/flowexec/flow/internal/io/common" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" ) func PrintVault(format, vaultName string) { diff --git a/internal/io/workspace/output.go b/internal/io/workspace/output.go index 1a197e9f..87ccb7b2 100644 --- a/internal/io/workspace/output.go +++ b/internal/io/workspace/output.go @@ -2,7 +2,7 @@ package workspace import ( "github.com/flowexec/flow/internal/io/common" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/workspace" ) diff --git a/internal/io/workspace/views.go b/internal/io/workspace/views.go index 9808f6e9..d33a9e69 100644 --- a/internal/io/workspace/views.go +++ b/internal/io/workspace/views.go @@ -9,10 +9,10 @@ import ( "github.com/flowexec/tuikit/types" "github.com/flowexec/tuikit/views" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" "github.com/flowexec/flow/internal/io/common" "github.com/flowexec/flow/internal/services/open" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" "github.com/flowexec/flow/types/workspace" ) diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index 6e6d6360..42c7211e 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -10,9 +10,9 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" - "github.com/flowexec/flow/internal/filesystem" flowMcp "github.com/flowexec/flow/internal/mcp" "github.com/flowexec/flow/internal/mcp/mocks" + "github.com/flowexec/flow/pkg/filesystem" ) func TestServer(t *testing.T) { diff --git a/internal/mcp/tools.go b/internal/mcp/tools.go index cbc50438..bf353190 100644 --- a/internal/mcp/tools.go +++ b/internal/mcp/tools.go @@ -12,7 +12,7 @@ import ( "github.com/mark3labs/mcp-go/server" "github.com/pkg/errors" - "github.com/flowexec/flow/internal/filesystem" + "github.com/flowexec/flow/pkg/filesystem" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/runner/exec/exec.go b/internal/runner/exec/exec.go index 47a38ee1..a6e8fac0 100644 --- a/internal/runner/exec/exec.go +++ b/internal/runner/exec/exec.go @@ -3,12 +3,12 @@ package exec import ( "github.com/pkg/errors" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/services/run" "github.com/flowexec/flow/internal/utils/env" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/runner/launch/launch.go b/internal/runner/launch/launch.go index 5072a131..e6e5f875 100644 --- a/internal/runner/launch/launch.go +++ b/internal/runner/launch/launch.go @@ -6,12 +6,12 @@ import ( "github.com/pkg/errors" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/services/open" "github.com/flowexec/flow/internal/utils" "github.com/flowexec/flow/internal/utils/env" + "github.com/flowexec/flow/pkg/context" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/runner/mocks/mock_runner.go b/internal/runner/mocks/mock_runner.go index bd949ebf..00db88ad 100644 --- a/internal/runner/mocks/mock_runner.go +++ b/internal/runner/mocks/mock_runner.go @@ -12,8 +12,8 @@ package mocks import ( reflect "reflect" - context "github.com/flowexec/flow/internal/context" engine "github.com/flowexec/flow/internal/runner/engine" + context "github.com/flowexec/flow/pkg/context" executable "github.com/flowexec/flow/types/executable" gomock "go.uber.org/mock/gomock" ) diff --git a/internal/runner/parallel/parallel.go b/internal/runner/parallel/parallel.go index 314c6d53..53819fef 100644 --- a/internal/runner/parallel/parallel.go +++ b/internal/runner/parallel/parallel.go @@ -12,13 +12,13 @@ import ( "github.com/pkg/errors" "golang.org/x/sync/errgroup" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/services/store" envUtils "github.com/flowexec/flow/internal/utils/env" execUtils "github.com/flowexec/flow/internal/utils/executables" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/runner/parallel/parallel_test.go b/internal/runner/parallel/parallel_test.go index 43b33343..49cfc35e 100644 --- a/internal/runner/parallel/parallel_test.go +++ b/internal/runner/parallel/parallel_test.go @@ -9,11 +9,11 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/runner/engine/mocks" "github.com/flowexec/flow/internal/runner/parallel" + "github.com/flowexec/flow/pkg/context" testUtils "github.com/flowexec/flow/tests/utils" "github.com/flowexec/flow/tests/utils/builder" "github.com/flowexec/flow/types/executable" diff --git a/internal/runner/render/render.go b/internal/runner/render/render.go index 7c172ce2..6e6457a5 100644 --- a/internal/runner/render/render.go +++ b/internal/runner/render/render.go @@ -12,12 +12,12 @@ import ( "github.com/pkg/errors" "gopkg.in/yaml.v3" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/io" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/utils/env" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/runner/request/request.go b/internal/runner/request/request.go index c213ff2f..6c00ac87 100644 --- a/internal/runner/request/request.go +++ b/internal/runner/request/request.go @@ -10,12 +10,12 @@ import ( "github.com/pkg/errors" "gopkg.in/yaml.v3" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/services/rest" "github.com/flowexec/flow/internal/utils/env" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/runner/runner.go b/internal/runner/runner.go index d23f6e1c..635c42ff 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -7,9 +7,9 @@ import ( "github.com/jahvon/expression" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner/engine" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index 3cf8fd86..6186aa08 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -8,11 +8,11 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" engMocks "github.com/flowexec/flow/internal/runner/engine/mocks" "github.com/flowexec/flow/internal/runner/mocks" + "github.com/flowexec/flow/pkg/context" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/runner/serial/serial.go b/internal/runner/serial/serial.go index 9ca902ba..10df16ce 100644 --- a/internal/runner/serial/serial.go +++ b/internal/runner/serial/serial.go @@ -10,13 +10,13 @@ import ( "github.com/jahvon/expression" "github.com/pkg/errors" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/services/store" envUtils "github.com/flowexec/flow/internal/utils/env" execUtils "github.com/flowexec/flow/internal/utils/executables" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/runner/serial/serial_test.go b/internal/runner/serial/serial_test.go index fe6d22a2..db2b0114 100644 --- a/internal/runner/serial/serial_test.go +++ b/internal/runner/serial/serial_test.go @@ -10,11 +10,11 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/runner/engine/mocks" "github.com/flowexec/flow/internal/runner/serial" + "github.com/flowexec/flow/pkg/context" testUtils "github.com/flowexec/flow/tests/utils" "github.com/flowexec/flow/tests/utils/builder" "github.com/flowexec/flow/types/executable" diff --git a/internal/services/store/store.go b/internal/services/store/store.go index 55d25c95..bec6991e 100644 --- a/internal/services/store/store.go +++ b/internal/services/store/store.go @@ -7,11 +7,10 @@ import ( "strings" "time" + "github.com/flowexec/flow/pkg/filesystem" "github.com/pkg/errors" bolt "go.etcd.io/bbolt" boltErrors "go.etcd.io/bbolt/errors" - - "github.com/flowexec/flow/internal/filesystem" ) const ( diff --git a/internal/templates/artifacts.go b/internal/templates/artifacts.go index 2ddfcef3..0f43ec05 100644 --- a/internal/templates/artifacts.go +++ b/internal/templates/artifacts.go @@ -10,8 +10,8 @@ import ( "github.com/jahvon/expression" "github.com/pkg/errors" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/templates/form.go b/internal/templates/form.go index 70aef8e8..c8887f44 100644 --- a/internal/templates/form.go +++ b/internal/templates/form.go @@ -5,8 +5,8 @@ import ( "github.com/flowexec/tuikit/views" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/io" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) @@ -47,7 +47,7 @@ func showForm(ctx *context.Context, fields executable.FormFields) error { ValidationExpr: f.Validate, }) } - form, err := views.NewForm(io.Theme(ctx.Config.Theme.String()), in, out, ff...) + form, err := views.NewForm(logger.Theme(ctx.Config.Theme.String()), in, out, ff...) if err != nil { return fmt.Errorf("encountered form init error: %w", err) } diff --git a/internal/templates/templates.go b/internal/templates/templates.go index c186f1df..4724eb0c 100644 --- a/internal/templates/templates.go +++ b/internal/templates/templates.go @@ -13,14 +13,14 @@ import ( "github.com/pkg/errors" "gopkg.in/yaml.v3" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner" "github.com/flowexec/flow/internal/runner/engine" "github.com/flowexec/flow/internal/utils" argUtils "github.com/flowexec/flow/internal/utils/env" execUtils "github.com/flowexec/flow/internal/utils/executables" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" ) diff --git a/internal/utils/env/env.go b/internal/utils/env/env.go index 8c305125..1c47900e 100644 --- a/internal/utils/env/env.go +++ b/internal/utils/env/env.go @@ -7,10 +7,10 @@ import ( "path/filepath" "strings" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" "github.com/flowexec/flow/internal/io" "github.com/flowexec/flow/internal/utils" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/utils/env/env_test.go b/internal/utils/env/env_test.go index f8012651..90b18baf 100644 --- a/internal/utils/env/env_test.go +++ b/internal/utils/env/env_test.go @@ -10,10 +10,10 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" - "github.com/flowexec/flow/internal/context" "github.com/flowexec/flow/internal/io" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/utils/env" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/config" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" diff --git a/internal/utils/executables/executables.go b/internal/utils/executables/executables.go index ab308c0d..bc01a653 100644 --- a/internal/utils/executables/executables.go +++ b/internal/utils/executables/executables.go @@ -3,7 +3,7 @@ package executables import ( "fmt" - "github.com/flowexec/flow/internal/context" + "github.com/flowexec/flow/pkg/context" "github.com/flowexec/flow/types/common" "github.com/flowexec/flow/types/executable" ) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index d12aff0c..ce32c8c0 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -10,7 +10,7 @@ import ( "github.com/pkg/errors" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" ) // ExpandPath expands a general path to an absolute path with security validation. diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go index b8e04581..61430e3e 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -10,8 +10,8 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/utils" + "github.com/flowexec/flow/pkg/logger" ) func TestUtils(t *testing.T) { diff --git a/internal/vault/vault.go b/internal/vault/vault.go index 1d0775d3..a8eac540 100644 --- a/internal/vault/vault.go +++ b/internal/vault/vault.go @@ -8,9 +8,9 @@ import ( "github.com/flowexec/vault" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/utils" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" ) const ( diff --git a/main.go b/main.go index fb8eed6e..13da032e 100644 --- a/main.go +++ b/main.go @@ -8,10 +8,11 @@ import ( "slices" "github.com/flowexec/flow/cmd" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" "github.com/flowexec/flow/internal/io" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/cli" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" ) @@ -29,7 +30,7 @@ func main() { loggerOpts := logger.InitOptions{ StdOut: io.Stdout, LogMode: cfg.DefaultLogMode, - Theme: io.Theme(cfg.Theme.String()), + Theme: logger.Theme(cfg.Theme.String()), ArchiveDirectory: archiveDir, } logger.Init(loggerOpts) @@ -49,7 +50,8 @@ func main() { if ctx == nil { panic("failed to initialize context") } - rootCmd := cmd.NewRootCmd(ctx) + rootCmd := cli.BuildRootCommand(ctx) + cli.RegisterAllCommands(ctx, rootCmd) if err := cmd.Execute(ctx, rootCmd); err != nil { logger.Log().FatalErr(err) } diff --git a/internal/cache/cache.go b/pkg/cache/cache.go similarity index 100% rename from internal/cache/cache.go rename to pkg/cache/cache.go diff --git a/internal/cache/cache_test.go b/pkg/cache/cache_test.go similarity index 100% rename from internal/cache/cache_test.go rename to pkg/cache/cache_test.go diff --git a/internal/cache/executables_cache.go b/pkg/cache/executables_cache.go similarity index 95% rename from internal/cache/executables_cache.go rename to pkg/cache/executables_cache.go index de036916..74259fc4 100644 --- a/internal/cache/executables_cache.go +++ b/pkg/cache/executables_cache.go @@ -7,8 +7,9 @@ import ( "gopkg.in/yaml.v3" "github.com/flowexec/flow/internal/fileparser" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/logger" + flowErrors "github.com/flowexec/flow/pkg/errors" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/common" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" @@ -16,7 +17,7 @@ import ( const execCacheKey = "executables" -//go:generate mockgen -destination=mocks/mock_executable_cache.go -package=mocks github.com/flowexec/flow/internal/cache ExecutableCache +//go:generate mockgen -destination=mocks/mock_executable_cache.go -package=mocks github.com/flowexec/flow/pkg/cache ExecutableCache type ExecutableCache interface { Update() error GetExecutableByRef(ref executable.Ref) (*executable.Executable, error) @@ -171,10 +172,10 @@ func (c *ExecutableCacheImpl) GetExecutableByRef(ref executable.Ref) (*executabl primaryRef = aliasedPrimaryRef cfgPath, found = c.Data.ExecutableMap[primaryRef] if !found { - return nil, NewExecutableNotFoundError(ref.String()) + return nil, flowErrors.NewExecutableNotFoundError(ref.String()) } } else { - return nil, NewExecutableNotFoundError(ref.String()) + return nil, flowErrors.NewExecutableNotFoundError(ref.String()) } } else { primaryRef = ref @@ -208,7 +209,7 @@ func (c *ExecutableCacheImpl) GetExecutableByRef(ref executable.Ref) (*executabl if err != nil { return nil, err } else if exec == nil { - return nil, NewExecutableNotFoundError(ref.String()) + return nil, flowErrors.NewExecutableNotFoundError(ref.String()) } c.Data.loadedExecutables[ref.String()] = exec diff --git a/internal/cache/executables_cache_test.go b/pkg/cache/executables_cache_test.go similarity index 97% rename from internal/cache/executables_cache_test.go rename to pkg/cache/executables_cache_test.go index 7611459c..da5607d2 100644 --- a/internal/cache/executables_cache_test.go +++ b/pkg/cache/executables_cache_test.go @@ -10,10 +10,10 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" - "github.com/flowexec/flow/internal/cache" - cacheMocks "github.com/flowexec/flow/internal/cache/mocks" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/cache" + cacheMocks "github.com/flowexec/flow/pkg/cache/mocks" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/common" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" @@ -262,7 +262,7 @@ var _ = Describe("ExecutableCacheImpl", func() { execRef := executable.Ref("exec test/testdata:test-alias") _, err := execCache.GetExecutableByRef(execRef) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("unable to find executable")) + Expect(err.Error()).To(ContainSubstring("executable not found")) // Should still be able to access via primary verb "run" runRef := executable.Ref("run test/testdata:test-alias") @@ -318,7 +318,7 @@ var _ = Describe("ExecutableCacheImpl", func() { executeRef := executable.Ref("execute test/testdata:test-alias") _, err = execCache.GetExecutableByRef(executeRef) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("unable to find executable")) + Expect(err.Error()).To(ContainSubstring("executable not found")) }) }) @@ -340,7 +340,7 @@ var _ = Describe("ExecutableCacheImpl", func() { execRef := executable.Ref("exec test/testdata:test-alias") _, err := execCache.GetExecutableByRef(execRef) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("unable to find executable")) + Expect(err.Error()).To(ContainSubstring("executable not found")) // Should still be able to access via primary verb "run" runRef := executable.Ref("run test/testdata:test-alias") diff --git a/internal/cache/mocks/mock_executable_cache.go b/pkg/cache/mocks/mock_executable_cache.go similarity index 95% rename from internal/cache/mocks/mock_executable_cache.go rename to pkg/cache/mocks/mock_executable_cache.go index 5c3f84b4..ada6bbaa 100644 --- a/internal/cache/mocks/mock_executable_cache.go +++ b/pkg/cache/mocks/mock_executable_cache.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/flowexec/flow/internal/cache (interfaces: ExecutableCache) +// Source: github.com/flowexec/flow/pkg/cache (interfaces: ExecutableCache) // // Generated by this command: // -// mockgen -destination=mocks/mock_executable_cache.go -package=mocks github.com/flowexec/flow/internal/cache ExecutableCache +// mockgen -destination=mocks/mock_executable_cache.go -package=mocks github.com/flowexec/flow/pkg/cache ExecutableCache // // Package mocks is a generated GoMock package. diff --git a/internal/cache/mocks/mock_workspace_cache.go b/pkg/cache/mocks/mock_workspace_cache.go similarity index 94% rename from internal/cache/mocks/mock_workspace_cache.go rename to pkg/cache/mocks/mock_workspace_cache.go index ed75b3c7..076c0e43 100644 --- a/internal/cache/mocks/mock_workspace_cache.go +++ b/pkg/cache/mocks/mock_workspace_cache.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/flowexec/flow/internal/cache (interfaces: WorkspaceCache) +// Source: github.com/flowexec/flow/pkg/cache (interfaces: WorkspaceCache) // // Generated by this command: // -// mockgen -destination=mocks/mock_workspace_cache.go -package=mocks github.com/flowexec/flow/internal/cache WorkspaceCache +// mockgen -destination=mocks/mock_workspace_cache.go -package=mocks github.com/flowexec/flow/pkg/cache WorkspaceCache // // Package mocks is a generated GoMock package. @@ -12,7 +12,7 @@ package mocks import ( reflect "reflect" - cache "github.com/flowexec/flow/internal/cache" + cache "github.com/flowexec/flow/pkg/cache" workspace "github.com/flowexec/flow/types/workspace" gomock "go.uber.org/mock/gomock" ) diff --git a/internal/cache/testdata/from-file.sh b/pkg/cache/testdata/from-file.sh similarity index 100% rename from internal/cache/testdata/from-file.sh rename to pkg/cache/testdata/from-file.sh diff --git a/internal/cache/workspaces_cache.go b/pkg/cache/workspaces_cache.go similarity index 94% rename from internal/cache/workspaces_cache.go rename to pkg/cache/workspaces_cache.go index b6f5d5ae..64dc796c 100644 --- a/internal/cache/workspaces_cache.go +++ b/pkg/cache/workspaces_cache.go @@ -4,14 +4,14 @@ import ( "github.com/pkg/errors" "gopkg.in/yaml.v3" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/workspace" ) const wsCacheKey = "workspace" -//go:generate mockgen -destination=mocks/mock_workspace_cache.go -package=mocks github.com/flowexec/flow/internal/cache WorkspaceCache +//go:generate mockgen -destination=mocks/mock_workspace_cache.go -package=mocks github.com/flowexec/flow/pkg/cache WorkspaceCache type WorkspaceCache interface { Update() error GetData() *WorkspaceCacheData diff --git a/internal/cache/workspaces_cache_test.go b/pkg/cache/workspaces_cache_test.go similarity index 94% rename from internal/cache/workspaces_cache_test.go rename to pkg/cache/workspaces_cache_test.go index d2b308a4..056036b5 100644 --- a/internal/cache/workspaces_cache_test.go +++ b/pkg/cache/workspaces_cache_test.go @@ -8,9 +8,9 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" - "github.com/flowexec/flow/internal/cache" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/cache" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/workspace" ) diff --git a/pkg/cli/builders.go b/pkg/cli/builders.go new file mode 100644 index 00000000..435bde27 --- /dev/null +++ b/pkg/cli/builders.go @@ -0,0 +1,51 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/flowexec/flow/cmd" + "github.com/flowexec/flow/pkg/context" +) + +// BuildRootCommand creates a new root command with optional configuration. +// The root command is the main entry point for the CLI. +// +// By default, this creates a root command with Flow's standard configuration. +// Use RootOption functions to customize the command. +func BuildRootCommand(ctx *context.Context, opts ...RootOption) *cobra.Command { + rootCmd := cmd.NewRootCmd(ctx) + + if len(opts) == 0 { + return rootCmd + } + + config := &RootConfig{} + for _, opt := range opts { + opt(config) + } + + if config.Use != "" { + rootCmd.Use = config.Use + } + if config.Short != "" { + rootCmd.Short = config.Short + } + if config.Long != "" { + rootCmd.Long = config.Long + } + if config.Version != "" { + rootCmd.Version = config.Version + } + + return rootCmd +} + +// RegisterAllCommands registers all Flow commands to the root command. +func RegisterAllCommands(ctx *context.Context, rootCmd *cobra.Command) { + cmd.RegisterSubCommands(ctx, rootCmd) +} + +// Execute runs the root command. This is a convenience wrapper around cobra's Execute. +func Execute(ctx *context.Context, rootCmd *cobra.Command) error { + return cmd.Execute(ctx, rootCmd) +} diff --git a/pkg/cli/builders_test.go b/pkg/cli/builders_test.go new file mode 100644 index 00000000..6a07656a --- /dev/null +++ b/pkg/cli/builders_test.go @@ -0,0 +1,112 @@ +package cli_test + +import ( + stdCtx "context" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/spf13/cobra" + + "github.com/flowexec/flow/internal/io" + "github.com/flowexec/flow/pkg/cli" + "github.com/flowexec/flow/pkg/context" +) + +func TestBuilders(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "CLI Builders Suite") +} + +var _ = Describe("BuildRootCommand", func() { + var ctx *context.Context + + BeforeEach(func() { + bkgCtx, cancelFunc := stdCtx.WithCancel(stdCtx.Background()) + ctx = context.NewContext(bkgCtx, cancelFunc, io.Stdin, io.Stdout) + }) + + AfterEach(func() { + if ctx != nil { + ctx.Finalize() + } + }) + + It("should create a root command with default configuration", func() { + rootCmd := cli.BuildRootCommand(ctx) + + Expect(rootCmd).NotTo(BeNil()) + Expect(rootCmd.Use).To(Equal("flow")) + Expect(rootCmd.Short).NotTo(BeEmpty()) + }) + + It("should apply custom use", func() { + rootCmd := cli.BuildRootCommand(ctx, cli.WithUse("mycli")) + + Expect(rootCmd.Use).To(Equal("mycli")) + }) + + It("should apply custom short description", func() { + rootCmd := cli.BuildRootCommand(ctx, cli.WithShort("Custom short")) + + Expect(rootCmd.Short).To(Equal("Custom short")) + }) + + It("should apply custom long description", func() { + rootCmd := cli.BuildRootCommand(ctx, cli.WithLong("Custom long")) + + Expect(rootCmd.Long).To(Equal("Custom long")) + }) + + It("should apply custom version", func() { + rootCmd := cli.BuildRootCommand(ctx, cli.WithVersion("1.0.0-custom")) + + Expect(rootCmd.Version).To(Equal("1.0.0-custom")) + }) + + It("should apply multiple options", func() { + rootCmd := cli.BuildRootCommand(ctx, + cli.WithUse("mycli"), + cli.WithShort("Custom short"), + cli.WithVersion("2.0.0"), + ) + + Expect(rootCmd.Use).To(Equal("mycli")) + Expect(rootCmd.Short).To(Equal("Custom short")) + Expect(rootCmd.Version).To(Equal("2.0.0")) + }) +}) + +var _ = Describe("RegisterAllCommands", func() { + var ctx *context.Context + var rootCmd *cobra.Command + + BeforeEach(func() { + bkgCtx, cancelFunc := stdCtx.WithCancel(stdCtx.Background()) + ctx = context.NewContext(bkgCtx, cancelFunc, io.Stdin, io.Stdout) + rootCmd = cli.BuildRootCommand(ctx) + }) + + AfterEach(func() { + if ctx != nil { + ctx.Finalize() + } + }) + + It("should register all commands", func() { + cli.RegisterAllCommands(ctx, rootCmd) + + commands := rootCmd.Commands() + Expect(commands).ToNot(BeEmpty()) + + // Check for key commands + commandNames := make(map[string]bool) + for _, cmd := range commands { + commandNames[cmd.Name()] = true + } + + Expect(commandNames).To(HaveKey("exec")) + Expect(commandNames).To(HaveKey("workspace")) + Expect(commandNames).To(HaveKey("config")) + }) +}) diff --git a/pkg/cli/doc.go b/pkg/cli/doc.go new file mode 100644 index 00000000..69c2d4bd --- /dev/null +++ b/pkg/cli/doc.go @@ -0,0 +1,59 @@ +// Package cli provides a public API for extending and customizing the Flow CLI. +// +// This package allows external projects to: +// - Build custom CLIs that include Flow's commands +// - Add cross-cutting hooks (PreRun/PostRun) to commands +// - Override existing commands with custom implementations +// - Add new commands alongside Flow's built-in commands +// +// # Basic Usage +// +// Build a custom CLI with all Flow commands: +// +// ctx := context.NewContext(...) +// rootCmd := cli.BuildRootCommand(ctx) +// cli.RegisterAllCommands(ctx, rootCmd) +// if err := cli.Execute(ctx, rootCmd); err != nil { +// log.Fatal(err) +// } +// +// # Customizing the Root Command +// +// Use functional options to customize the root command: +// +// rootCmd := cli.BuildRootCommand(ctx, +// cli.WithVersion("1.0.0-custom"), +// cli.WithShort("My custom Flow CLI"), +// cli.WithPersistentPreRun(myPreRunHook), +// ) +// +// # Adding Hooks +// +// Add hooks to individual commands or recursively to all commands: +// +// // Add to a single command +// cli.AddPreRunHook(cmd, telemetryHook) +// +// // Add to all commands recursively +// cli.ApplyHooksRecursive(rootCmd, preHook, postHook) +// +// # Command Registry Operations +// +// Manipulate the command tree: +// +// // Find a command +// wsCmd := cli.FindCommand(rootCmd, "workspace") +// +// // Replace a command +// cli.ReplaceCommand(rootCmd, "exec", customExecCmd) +// +// // Walk all commands +// cli.WalkCommands(rootCmd, func(cmd *cobra.Command) { +// // Do something with each command +// }) +// +// # Thread Safety +// +// This package is not thread-safe. Command building and modification should be +// done during CLI initialization, not concurrently. +package cli diff --git a/pkg/cli/example/main.go b/pkg/cli/example/main.go new file mode 100644 index 00000000..a314c95f --- /dev/null +++ b/pkg/cli/example/main.go @@ -0,0 +1,87 @@ +// Package main demonstrates basic usage of the Flow CLI extension API. +// +// This example shows how to: +// - Create a context +// - Build a root command with custom configuration +// - Register all Flow commands +// - Execute the CLI +package main + +import ( + stdCtx "context" + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/flowexec/flow/pkg/cli" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" +) + +func main() { + // Load Flow configuration + cfg, err := filesystem.LoadConfig() + if err != nil { + panic(fmt.Errorf("user config load error: %w", err)) + } + + // Initialize logger + loggerOpts := logger.InitOptions{ + StdOut: os.Stdout, + LogMode: cfg.DefaultLogMode, + Theme: logger.Theme(cfg.Theme.String()), + } + logger.Init(loggerOpts) + defer logger.Log().Flush() + + // Create context + bkgCtx, cancelFunc := stdCtx.WithCancel(stdCtx.Background()) + ctx := context.NewContext(bkgCtx, cancelFunc, os.Stdin, os.Stdout) + defer ctx.Finalize() + + // Build root command with custom version + rootCmd := cli.BuildRootCommand(ctx, + cli.WithVersion("1.0.0-example"), + cli.WithShort("Flow CLI - Basic Extension Example"), + ) + + // Register all Flow commands + cli.RegisterAllCommands(ctx, rootCmd) + + // Add a persistent hook to the root for global initialization/cleanup + cli.AddPersistentPreRunHook(rootCmd, func(cmd *cobra.Command, args []string) { + logger.Log().Debugf("Global PreRun: Initializing resources") + }) + + cli.AddPersistentPostRunHook(rootCmd, func(cmd *cobra.Command, args []string) { + logger.Log().Debugf("Global PostRun: Cleaning up resources") + }) + + // Add a custom command + premiumCmd := &cobra.Command{ + Use: "premium", + Short: "Premium features", + Long: "Access premium features of the Flow CLI", + Run: func(cmd *cobra.Command, args []string) { + logger.Log().PlainTextInfo("Welcome to Premium Flow CLI!") + logger.Log().PlainTextInfo("This is a custom command added via the extension API.") + }, + } + + rootCmd.AddCommand(premiumCmd) + + // Walk all commands and add annotations + cli.WalkCommands(rootCmd, func(cmd *cobra.Command) { + if cmd.Annotations == nil { + cmd.Annotations = make(map[string]string) + } + cmd.Annotations["edition"] = "customized" + }) + + // Execute + if err := cli.Execute(ctx, rootCmd); err != nil { + logger.Log().FatalErr(err) + } +} diff --git a/pkg/cli/hooks.go b/pkg/cli/hooks.go new file mode 100644 index 00000000..047fd45b --- /dev/null +++ b/pkg/cli/hooks.go @@ -0,0 +1,83 @@ +package cli + +import "github.com/spf13/cobra" + +// AddPreRunHook adds a PreRun hook to a command, preserving any existing PreRun hook. +// If the command already has a PreRun hook, the new hook will run before it. +func AddPreRunHook(cmd *cobra.Command, hook HookFunc) { + if cmd == nil || hook == nil { + return + } + + existingHook := cmd.PreRun + if existingHook == nil { + cmd.PreRun = hook + return + } + + cmd.PreRun = func(c *cobra.Command, args []string) { + hook(c, args) + existingHook(c, args) + } +} + +// AddPostRunHook adds a PostRun hook to a command, preserving any existing PostRun hook. +// If the command already has a PostRun hook, the new hook will run after it. +func AddPostRunHook(cmd *cobra.Command, hook HookFunc) { + if cmd == nil || hook == nil { + return + } + + existingHook := cmd.PostRun + if existingHook == nil { + cmd.PostRun = hook + return + } + + cmd.PostRun = func(c *cobra.Command, args []string) { + existingHook(c, args) + hook(c, args) + } +} + +// AddPersistentPreRunHook adds a PersistentPreRun hook to a command, preserving any existing hook. +// If the command already has a PersistentPreRun hook, the new hook will run before it. +// +// PersistentPreRun hooks run before all commands in the subtree. +func AddPersistentPreRunHook(cmd *cobra.Command, hook HookFunc) { + if cmd == nil || hook == nil { + return + } + + existingHook := cmd.PersistentPreRun + if existingHook == nil { + cmd.PersistentPreRun = hook + return + } + + cmd.PersistentPreRun = func(c *cobra.Command, args []string) { + hook(c, args) + existingHook(c, args) + } +} + +// AddPersistentPostRunHook adds a PersistentPostRun hook to a command, preserving any existing hook. +// If the command already has a PersistentPostRun hook, the new hook will run after it. +// +// PersistentPostRun hooks run after all commands in the subtree. +func AddPersistentPostRunHook(cmd *cobra.Command, hook HookFunc) { + if cmd == nil || hook == nil { + return + } + + existingHook := cmd.PersistentPostRun + if existingHook == nil { + cmd.PersistentPostRun = hook + return + } + + cmd.PersistentPostRun = func(c *cobra.Command, args []string) { + existingHook(c, args) + hook(c, args) + } +} diff --git a/pkg/cli/hooks_test.go b/pkg/cli/hooks_test.go new file mode 100644 index 00000000..259278a0 --- /dev/null +++ b/pkg/cli/hooks_test.go @@ -0,0 +1,160 @@ +package cli_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/spf13/cobra" + + "github.com/flowexec/flow/pkg/cli" +) + +var _ = Describe("Hook Injection", func() { + Describe("AddPreRunHook", func() { + It("should add PreRun hook to command without existing hook", func() { + cmd := &cobra.Command{Use: "test"} + hookCalled := false + + cli.AddPreRunHook(cmd, func(c *cobra.Command, args []string) { + hookCalled = true + }) + + Expect(cmd.PreRun).NotTo(BeNil()) + cmd.PreRun(cmd, []string{}) + Expect(hookCalled).To(BeTrue()) + }) + + It("should chain with existing PreRun hook", func() { + cmd := &cobra.Command{Use: "test"} + firstCalled := false + secondCalled := false + callOrder := []int{} + + cmd.PreRun = func(c *cobra.Command, args []string) { + firstCalled = true + callOrder = append(callOrder, 1) + } + + cli.AddPreRunHook(cmd, func(c *cobra.Command, args []string) { + secondCalled = true + callOrder = append(callOrder, 2) + }) + + cmd.PreRun(cmd, []string{}) + Expect(firstCalled).To(BeTrue()) + Expect(secondCalled).To(BeTrue()) + Expect(callOrder).To(Equal([]int{2, 1}), "New hook should run before existing hook") + }) + + It("should handle nil command gracefully", func() { + Expect(func() { + cli.AddPreRunHook(nil, func(c *cobra.Command, args []string) {}) + }).NotTo(Panic()) + }) + + It("should handle nil hook gracefully", func() { + cmd := &cobra.Command{Use: "test"} + Expect(func() { + cli.AddPreRunHook(cmd, nil) + }).NotTo(Panic()) + }) + }) + + Describe("AddPostRunHook", func() { + It("should add PostRun hook to command without existing hook", func() { + cmd := &cobra.Command{Use: "test"} + hookCalled := false + + cli.AddPostRunHook(cmd, func(c *cobra.Command, args []string) { + hookCalled = true + }) + + Expect(cmd.PostRun).NotTo(BeNil()) + cmd.PostRun(cmd, []string{}) + Expect(hookCalled).To(BeTrue()) + }) + + It("should chain with existing PostRun hook", func() { + cmd := &cobra.Command{Use: "test"} + firstCalled := false + secondCalled := false + callOrder := []int{} + + cmd.PostRun = func(c *cobra.Command, args []string) { + firstCalled = true + callOrder = append(callOrder, 1) + } + + cli.AddPostRunHook(cmd, func(c *cobra.Command, args []string) { + secondCalled = true + callOrder = append(callOrder, 2) + }) + + cmd.PostRun(cmd, []string{}) + Expect(firstCalled).To(BeTrue()) + Expect(secondCalled).To(BeTrue()) + Expect(callOrder).To(Equal([]int{1, 2}), "New hook should run after existing hook") + }) + }) + + Describe("AddPersistentPreRunHook", func() { + It("should add PersistentPreRun hook", func() { + cmd := &cobra.Command{Use: "test"} + hookCalled := false + + cli.AddPersistentPreRunHook(cmd, func(c *cobra.Command, args []string) { + hookCalled = true + }) + + Expect(cmd.PersistentPreRun).NotTo(BeNil()) + cmd.PersistentPreRun(cmd, []string{}) + Expect(hookCalled).To(BeTrue()) + }) + + It("should chain with existing PersistentPreRun hook", func() { + cmd := &cobra.Command{Use: "test"} + callOrder := []int{} + + cmd.PersistentPreRun = func(c *cobra.Command, args []string) { + callOrder = append(callOrder, 1) + } + + cli.AddPersistentPreRunHook(cmd, func(c *cobra.Command, args []string) { + callOrder = append(callOrder, 2) + }) + + cmd.PersistentPreRun(cmd, []string{}) + Expect(callOrder).To(Equal([]int{2, 1})) + }) + }) + + Describe("AddPersistentPostRunHook", func() { + It("should add PersistentPostRun hook", func() { + cmd := &cobra.Command{Use: "test"} + hookCalled := false + + cli.AddPersistentPostRunHook(cmd, func(c *cobra.Command, args []string) { + hookCalled = true + }) + + Expect(cmd.PersistentPostRun).NotTo(BeNil()) + cmd.PersistentPostRun(cmd, []string{}) + Expect(hookCalled).To(BeTrue()) + }) + + It("should chain with existing PersistentPostRun hook", func() { + cmd := &cobra.Command{Use: "test"} + callOrder := []int{} + + cmd.PersistentPostRun = func(c *cobra.Command, args []string) { + callOrder = append(callOrder, 1) + } + + cli.AddPersistentPostRunHook(cmd, func(c *cobra.Command, args []string) { + callOrder = append(callOrder, 2) + }) + + cmd.PersistentPostRun(cmd, []string{}) + Expect(callOrder).To(Equal([]int{1, 2})) + }) + }) +}) diff --git a/pkg/cli/registry.go b/pkg/cli/registry.go new file mode 100644 index 00000000..61662cbb --- /dev/null +++ b/pkg/cli/registry.go @@ -0,0 +1,163 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// WalkCommands traverses the command tree starting from the root and applies +// a function to each command (including the root). +// +// The traversal is depth-first, visiting parent commands before their children. +func WalkCommands(rootCmd *cobra.Command, fn func(*cobra.Command)) { + if rootCmd == nil || fn == nil { + return + } + + // Apply function to current command + fn(rootCmd) + + // Recursively apply to all subcommands + for _, subCmd := range rootCmd.Commands() { + WalkCommands(subCmd, fn) + } +} + +// FindCommand finds a command by name in the command tree. +// It performs a breadth-first search starting from the root. +// +// Returns nil if the command is not found. +func FindCommand(rootCmd *cobra.Command, name string) *cobra.Command { + if rootCmd == nil || name == "" { + return nil + } + + if rootCmd.Name() == name { + return rootCmd + } + + // Search in immediate children first (breadth-first) + for _, cmd := range rootCmd.Commands() { + if cmd.Name() == name { + return cmd + } + } + + // Search recursively in subcommands + for _, cmd := range rootCmd.Commands() { + if found := FindCommand(cmd, name); found != nil { + return found + } + } + + return nil +} + +// FindCommandPath finds a command by its full path (e.g., "workspace add"). +// The path should be space-separated command names. +// +// Returns nil if the command is not found. +func FindCommandPath(rootCmd *cobra.Command, path string) *cobra.Command { + if rootCmd == nil || path == "" { + return nil + } + + // Use cobra's built-in Find method + cmd, _, err := rootCmd.Find(splitPath(path)) + if err != nil { + return nil + } + return cmd +} + +// ReplaceCommand replaces a command with the given name with a new command. +// The new command will be added to the same parent as the old command. +// +// Returns an error if the old command is not found or if the replacement fails. +func ReplaceCommand(rootCmd *cobra.Command, oldName string, newCmd *cobra.Command) error { + if rootCmd == nil || oldName == "" || newCmd == nil { + return fmt.Errorf("invalid parameters: rootCmd, oldName, and newCmd must not be nil/empty") + } + + oldCmd := FindCommand(rootCmd, oldName) + if oldCmd == nil { + return fmt.Errorf("command %q not found", oldName) + } + + parent := oldCmd.Parent() + if parent == nil { + return fmt.Errorf("cannot replace root command") + } + + parent.RemoveCommand(oldCmd) + parent.AddCommand(newCmd) + + return nil +} + +// RemoveCommand removes a command by name from the command tree. +// +// Returns an error if the command is not found or if it's the root command. +func RemoveCommand(rootCmd *cobra.Command, name string) error { + if rootCmd == nil || name == "" { + return fmt.Errorf("invalid parameters: rootCmd and name must not be nil/empty") + } + + cmd := FindCommand(rootCmd, name) + if cmd == nil { + return fmt.Errorf("command %q not found", name) + } + + parent := cmd.Parent() + if parent == nil { + return fmt.Errorf("cannot remove root command") + } + + parent.RemoveCommand(cmd) + + return nil +} + +// ListCommands returns a list of all command names in the tree. +// The list includes the root command and all subcommands. +func ListCommands(rootCmd *cobra.Command) []string { + var names []string + WalkCommands(rootCmd, func(cmd *cobra.Command) { + names = append(names, cmd.Name()) + }) + return names +} + +// GetSubcommands returns a map of subcommand names to commands for a given command. +// This is a convenience wrapper around cobra's Commands() method. +func GetSubcommands(cmd *cobra.Command) map[string]*cobra.Command { + if cmd == nil { + return nil + } + + subCmds := make(map[string]*cobra.Command) + for _, subCmd := range cmd.Commands() { + subCmds[subCmd.Name()] = subCmd + } + return subCmds +} + +func splitPath(path string) []string { + var parts []string + current := "" + for _, ch := range path { + if ch == ' ' { + if current != "" { + parts = append(parts, current) + current = "" + } + } else { + current += string(ch) + } + } + if current != "" { + parts = append(parts, current) + } + return parts +} diff --git a/pkg/cli/registry_test.go b/pkg/cli/registry_test.go new file mode 100644 index 00000000..c363519e --- /dev/null +++ b/pkg/cli/registry_test.go @@ -0,0 +1,245 @@ +package cli_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/spf13/cobra" + + "github.com/flowexec/flow/pkg/cli" +) + +var _ = Describe("Command Registry", func() { + Describe("WalkCommands", func() { + It("should traverse all commands", func() { + rootCmd := &cobra.Command{Use: "root"} + sub1 := &cobra.Command{Use: "sub1"} + sub2 := &cobra.Command{Use: "sub2"} + subsub := &cobra.Command{Use: "subsub"} + sub1.AddCommand(subsub) + rootCmd.AddCommand(sub1, sub2) + + visited := []string{} + cli.WalkCommands(rootCmd, func(cmd *cobra.Command) { + visited = append(visited, cmd.Use) + }) + + Expect(visited).To(Equal([]string{"root", "sub1", "subsub", "sub2"})) + }) + + It("should handle nil command gracefully", func() { + Expect(func() { + cli.WalkCommands(nil, func(cmd *cobra.Command) {}) + }).NotTo(Panic()) + }) + + It("should handle nil function gracefully", func() { + cmd := &cobra.Command{Use: "test"} + Expect(func() { + cli.WalkCommands(cmd, nil) + }).NotTo(Panic()) + }) + }) + + Describe("FindCommand", func() { + var rootCmd *cobra.Command + + BeforeEach(func() { + rootCmd = &cobra.Command{Use: "root"} + sub1 := &cobra.Command{Use: "sub1"} + sub2 := &cobra.Command{Use: "sub2"} + subsub := &cobra.Command{Use: "subsub"} + sub1.AddCommand(subsub) + rootCmd.AddCommand(sub1, sub2) + }) + + It("should find root command", func() { + cmd := cli.FindCommand(rootCmd, "root") + Expect(cmd).NotTo(BeNil()) + Expect(cmd.Use).To(Equal("root")) + }) + + It("should find immediate child", func() { + cmd := cli.FindCommand(rootCmd, "sub1") + Expect(cmd).NotTo(BeNil()) + Expect(cmd.Use).To(Equal("sub1")) + }) + + It("should find nested command", func() { + cmd := cli.FindCommand(rootCmd, "subsub") + Expect(cmd).NotTo(BeNil()) + Expect(cmd.Use).To(Equal("subsub")) + }) + + It("should return nil for non-existent command", func() { + cmd := cli.FindCommand(rootCmd, "nonexistent") + Expect(cmd).To(BeNil()) + }) + + It("should handle nil root command", func() { + cmd := cli.FindCommand(nil, "test") + Expect(cmd).To(BeNil()) + }) + + It("should handle empty name", func() { + cmd := cli.FindCommand(rootCmd, "") + Expect(cmd).To(BeNil()) + }) + }) + + Describe("FindCommandPath", func() { + var rootCmd *cobra.Command + + BeforeEach(func() { + rootCmd = &cobra.Command{Use: "root"} + sub1 := &cobra.Command{Use: "sub1"} + subsub := &cobra.Command{Use: "subsub"} + sub1.AddCommand(subsub) + rootCmd.AddCommand(sub1) + }) + + It("should find command by single name", func() { + cmd := cli.FindCommandPath(rootCmd, "sub1") + Expect(cmd).NotTo(BeNil()) + Expect(cmd.Use).To(Equal("sub1")) + }) + + It("should find command by path", func() { + cmd := cli.FindCommandPath(rootCmd, "sub1 subsub") + Expect(cmd).NotTo(BeNil()) + Expect(cmd.Use).To(Equal("subsub")) + }) + + It("should return nil for invalid path", func() { + cmd := cli.FindCommandPath(rootCmd, "nonexistent path") + Expect(cmd).To(BeNil()) + }) + }) + + Describe("ReplaceCommand", func() { + var rootCmd *cobra.Command + + BeforeEach(func() { + rootCmd = &cobra.Command{Use: "root"} + oldCmd := &cobra.Command{Use: "old"} + rootCmd.AddCommand(oldCmd) + }) + + It("should replace existing command", func() { + newCmd := &cobra.Command{ + Use: "old", + Short: "New command", + } + + err := cli.ReplaceCommand(rootCmd, "old", newCmd) + Expect(err).NotTo(HaveOccurred()) + + found := cli.FindCommand(rootCmd, "old") + Expect(found).NotTo(BeNil()) + Expect(found.Short).To(Equal("New command")) + }) + + It("should return error for non-existent command", func() { + newCmd := &cobra.Command{Use: "new"} + err := cli.ReplaceCommand(rootCmd, "nonexistent", newCmd) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("not found")) + }) + + It("should return error for nil root", func() { + newCmd := &cobra.Command{Use: "new"} + err := cli.ReplaceCommand(nil, "old", newCmd) + Expect(err).To(HaveOccurred()) + }) + + It("should return error for nil new command", func() { + err := cli.ReplaceCommand(rootCmd, "old", nil) + Expect(err).To(HaveOccurred()) + }) + + It("should return error when trying to replace root", func() { + newCmd := &cobra.Command{Use: "root"} + err := cli.ReplaceCommand(rootCmd, "root", newCmd) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("cannot replace root")) + }) + }) + + Describe("RemoveCommand", func() { + var rootCmd *cobra.Command + + BeforeEach(func() { + rootCmd = &cobra.Command{Use: "root"} + cmd1 := &cobra.Command{Use: "cmd1"} + cmd2 := &cobra.Command{Use: "cmd2"} + rootCmd.AddCommand(cmd1, cmd2) + }) + + It("should remove existing command", func() { + err := cli.RemoveCommand(rootCmd, "cmd1") + Expect(err).NotTo(HaveOccurred()) + + found := cli.FindCommand(rootCmd, "cmd1") + Expect(found).To(BeNil()) + + // cmd2 should still exist + found = cli.FindCommand(rootCmd, "cmd2") + Expect(found).NotTo(BeNil()) + }) + + It("should return error for non-existent command", func() { + err := cli.RemoveCommand(rootCmd, "nonexistent") + Expect(err).To(HaveOccurred()) + }) + + It("should return error when trying to remove root", func() { + err := cli.RemoveCommand(rootCmd, "root") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("cannot remove root")) + }) + }) + + Describe("ListCommands", func() { + It("should list all command names", func() { + rootCmd := &cobra.Command{Use: "root"} + sub1 := &cobra.Command{Use: "sub1"} + sub2 := &cobra.Command{Use: "sub2"} + rootCmd.AddCommand(sub1, sub2) + + names := cli.ListCommands(rootCmd) + Expect(names).To(ConsistOf("root", "sub1", "sub2")) + }) + + It("should handle empty tree", func() { + rootCmd := &cobra.Command{Use: "root"} + names := cli.ListCommands(rootCmd) + Expect(names).To(Equal([]string{"root"})) + }) + }) + + Describe("GetSubcommands", func() { + It("should return map of subcommands", func() { + rootCmd := &cobra.Command{Use: "root"} + sub1 := &cobra.Command{Use: "sub1"} + sub2 := &cobra.Command{Use: "sub2"} + rootCmd.AddCommand(sub1, sub2) + + subCmds := cli.GetSubcommands(rootCmd) + Expect(subCmds).To(HaveLen(2)) + Expect(subCmds).To(HaveKey("sub1")) + Expect(subCmds).To(HaveKey("sub2")) + Expect(subCmds["sub1"]).To(Equal(sub1)) + Expect(subCmds["sub2"]).To(Equal(sub2)) + }) + + It("should return empty map for command with no subcommands", func() { + cmd := &cobra.Command{Use: "test"} + subCmds := cli.GetSubcommands(cmd) + Expect(subCmds).To(BeEmpty()) + }) + + It("should return nil for nil command", func() { + subCmds := cli.GetSubcommands(nil) + Expect(subCmds).To(BeNil()) + }) + }) +}) diff --git a/pkg/cli/types.go b/pkg/cli/types.go new file mode 100644 index 00000000..79f15ef3 --- /dev/null +++ b/pkg/cli/types.go @@ -0,0 +1,53 @@ +package cli + +import "github.com/spf13/cobra" + +// HookFunc is a function that can be used as a PreRun or PostRun hook for a command. +// It receives the command being executed and its arguments. +type HookFunc func(cmd *cobra.Command, args []string) + +// RootConfig holds configuration options for building the root command. +type RootConfig struct { + // Use is the one-line usage message (defaults to "flow") + Use string + + // Short is the short description shown in help (defaults to Flow's standard description) + Short string + + // Long is the long description shown in help (defaults to Flow's standard description) + Long string + + // Version is the version string (defaults to Flow's current version) + Version string +} + +// RootOption is a functional option for configuring the root command. +type RootOption func(*RootConfig) + +// WithUse sets the Use field for the root command. +func WithUse(use string) RootOption { + return func(c *RootConfig) { + c.Use = use + } +} + +// WithShort sets the Short description for the root command. +func WithShort(short string) RootOption { + return func(c *RootConfig) { + c.Short = short + } +} + +// WithLong sets the Long description for the root command. +func WithLong(long string) RootOption { + return func(c *RootConfig) { + c.Long = long + } +} + +// WithVersion sets the Version string for the root command. +func WithVersion(version string) RootOption { + return func(c *RootConfig) { + c.Version = version + } +} diff --git a/internal/context/context.go b/pkg/context/context.go similarity index 97% rename from internal/context/context.go rename to pkg/context/context.go index 6d499ca9..a686694b 100644 --- a/internal/context/context.go +++ b/pkg/context/context.go @@ -11,10 +11,9 @@ import ( "github.com/flowexec/tuikit/themes" "github.com/pkg/errors" - "github.com/flowexec/flow/internal/cache" - "github.com/flowexec/flow/internal/filesystem" - flowIO "github.com/flowexec/flow/internal/io" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/cache" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/config" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" @@ -85,7 +84,7 @@ func NewContext(ctx context.Context, cancelFunc context.CancelFunc, stdIn, stdOu tuikit.WithLoadingMsg("thinking..."), ) - theme := flowIO.Theme(cfg.Theme.String()) + theme := logger.Theme(cfg.Theme.String()) if cfg.ColorOverride != nil { theme = overrideThemeColor(theme, cfg.ColorOverride) } diff --git a/internal/context/context_test.go b/pkg/context/context_test.go similarity index 100% rename from internal/context/context_test.go rename to pkg/context/context_test.go diff --git a/internal/errors/errors.go b/pkg/errors/errors.go similarity index 54% rename from internal/errors/errors.go rename to pkg/errors/errors.go index 5163e5f4..f8e868fa 100644 --- a/internal/errors/errors.go +++ b/pkg/errors/errors.go @@ -5,12 +5,15 @@ import ( ) type ExecutableNotFoundError struct { - Verb string - Name string + Ref string } func (e ExecutableNotFoundError) Error() string { - return fmt.Sprintf("%s executable %s not found", e.Verb, e.Name) + return fmt.Sprintf("%s executable not found", e.Ref) +} + +func NewExecutableNotFoundError(ref string) ExecutableNotFoundError { + return ExecutableNotFoundError{Ref: ref} } type WorkspaceNotFoundError struct { @@ -34,3 +37,19 @@ func (e ExecutableContextError) Error() string { e.FlowFile, ) } + +type CacheUpdateError struct { + Err error +} + +func (e CacheUpdateError) Error() string { + return fmt.Sprintf("unable to update cache - %v", e.Err) +} + +func (e CacheUpdateError) Unwrap() error { + return e.Err +} + +func NewCacheUpdateError(err error) CacheUpdateError { + return CacheUpdateError{Err: err} +} diff --git a/internal/filesystem/cache.go b/pkg/filesystem/cache.go similarity index 100% rename from internal/filesystem/cache.go rename to pkg/filesystem/cache.go diff --git a/internal/filesystem/cache_test.go b/pkg/filesystem/cache_test.go similarity index 96% rename from internal/filesystem/cache_test.go rename to pkg/filesystem/cache_test.go index 54fcd8e9..339d6a31 100644 --- a/internal/filesystem/cache_test.go +++ b/pkg/filesystem/cache_test.go @@ -4,10 +4,9 @@ import ( "os" "path/filepath" + "github.com/flowexec/flow/pkg/filesystem" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "github.com/flowexec/flow/internal/filesystem" ) var _ = Describe("Cache", func() { diff --git a/internal/filesystem/config.go b/pkg/filesystem/config.go similarity index 100% rename from internal/filesystem/config.go rename to pkg/filesystem/config.go diff --git a/internal/filesystem/config_test.go b/pkg/filesystem/config_test.go similarity index 97% rename from internal/filesystem/config_test.go rename to pkg/filesystem/config_test.go index 574f9e09..c77becf7 100644 --- a/internal/filesystem/config_test.go +++ b/pkg/filesystem/config_test.go @@ -7,7 +7,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/flowexec/flow/internal/filesystem" + "github.com/flowexec/flow/pkg/filesystem" "github.com/flowexec/flow/types/config" ) diff --git a/internal/filesystem/executables.go b/pkg/filesystem/executables.go similarity index 99% rename from internal/filesystem/executables.go rename to pkg/filesystem/executables.go index 5cd869b6..77a8961a 100644 --- a/internal/filesystem/executables.go +++ b/pkg/filesystem/executables.go @@ -10,7 +10,7 @@ import ( "github.com/pkg/errors" "gopkg.in/yaml.v3" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" ) diff --git a/internal/filesystem/executables_test.go b/pkg/filesystem/executables_test.go similarity index 98% rename from internal/filesystem/executables_test.go rename to pkg/filesystem/executables_test.go index 18717978..7be3ee6f 100644 --- a/internal/filesystem/executables_test.go +++ b/pkg/filesystem/executables_test.go @@ -9,8 +9,8 @@ import ( . "github.com/onsi/gomega" "go.uber.org/mock/gomock" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" ) diff --git a/internal/filesystem/filesystem_test.go b/pkg/filesystem/filesystem_test.go similarity index 100% rename from internal/filesystem/filesystem_test.go rename to pkg/filesystem/filesystem_test.go diff --git a/internal/filesystem/helpers.go b/pkg/filesystem/helpers.go similarity index 100% rename from internal/filesystem/helpers.go rename to pkg/filesystem/helpers.go diff --git a/internal/filesystem/logs.go b/pkg/filesystem/logs.go similarity index 100% rename from internal/filesystem/logs.go rename to pkg/filesystem/logs.go diff --git a/internal/filesystem/logs_test.go b/pkg/filesystem/logs_test.go similarity index 92% rename from internal/filesystem/logs_test.go rename to pkg/filesystem/logs_test.go index e25941f6..942aa29f 100644 --- a/internal/filesystem/logs_test.go +++ b/pkg/filesystem/logs_test.go @@ -6,7 +6,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/flowexec/flow/internal/filesystem" + "github.com/flowexec/flow/pkg/filesystem" ) var _ = Describe("Logs", func() { diff --git a/internal/filesystem/templates.go b/pkg/filesystem/templates.go similarity index 100% rename from internal/filesystem/templates.go rename to pkg/filesystem/templates.go diff --git a/internal/filesystem/templates_test.go b/pkg/filesystem/templates_test.go similarity index 98% rename from internal/filesystem/templates_test.go rename to pkg/filesystem/templates_test.go index 7e65b2b0..1ab17ac9 100644 --- a/internal/filesystem/templates_test.go +++ b/pkg/filesystem/templates_test.go @@ -10,7 +10,7 @@ import ( "github.com/pkg/errors" "gopkg.in/yaml.v3" - "github.com/flowexec/flow/internal/filesystem" + "github.com/flowexec/flow/pkg/filesystem" "github.com/flowexec/flow/types/executable" "github.com/flowexec/flow/types/workspace" ) diff --git a/internal/filesystem/workspace.go b/pkg/filesystem/workspace.go similarity index 100% rename from internal/filesystem/workspace.go rename to pkg/filesystem/workspace.go diff --git a/internal/filesystem/workspace_test.go b/pkg/filesystem/workspace_test.go similarity index 96% rename from internal/filesystem/workspace_test.go rename to pkg/filesystem/workspace_test.go index 55287e9d..fed8558f 100644 --- a/internal/filesystem/workspace_test.go +++ b/pkg/filesystem/workspace_test.go @@ -7,7 +7,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/flowexec/flow/internal/filesystem" + "github.com/flowexec/flow/pkg/filesystem" "github.com/flowexec/flow/types/workspace" ) diff --git a/internal/logger/logger.go b/pkg/logger/logger.go similarity index 100% rename from internal/logger/logger.go rename to pkg/logger/logger.go diff --git a/internal/logger/logger_test.go b/pkg/logger/logger_test.go similarity index 92% rename from internal/logger/logger_test.go rename to pkg/logger/logger_test.go index 540bfe08..bca7d021 100644 --- a/internal/logger/logger_test.go +++ b/pkg/logger/logger_test.go @@ -8,7 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/flowexec/flow/internal/logger" + "github.com/flowexec/flow/pkg/logger" ) func TestLogger(t *testing.T) { diff --git a/internal/io/styles.go b/pkg/logger/theme.go similarity index 93% rename from internal/io/styles.go rename to pkg/logger/theme.go index c3d6553d..974bc832 100644 --- a/internal/io/styles.go +++ b/pkg/logger/theme.go @@ -1,4 +1,4 @@ -package io +package logger import "github.com/flowexec/tuikit/themes" diff --git a/tests/browse_cmds_e2e_test.go b/tests/browse_cmds_e2e_test.go index 401970ad..231fed70 100644 --- a/tests/browse_cmds_e2e_test.go +++ b/tests/browse_cmds_e2e_test.go @@ -15,9 +15,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/flowexec/flow/internal/io" execIO "github.com/flowexec/flow/internal/io/executable" "github.com/flowexec/flow/internal/io/library" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/tests/utils" "github.com/flowexec/flow/types/executable" ) @@ -60,7 +60,7 @@ var _ = Describe("browse TUI", func() { libraryView := library.NewLibraryView( ctx.Context, wsList, execList, library.Filter{}, - io.Theme(ctx.Config.Theme.String()), + logger.Theme(ctx.Config.Theme.String()), runFunc, ) Expect(container.SetView(libraryView)).To(Succeed()) @@ -89,7 +89,7 @@ var _ = Describe("browse TUI", func() { libraryView := library.NewLibraryView( ctx.Context, wsList, execList, library.Filter{}, - io.Theme(ctx.Config.Theme.String()), + logger.Theme(ctx.Config.Theme.String()), runFunc, ) Expect(container.SetView(libraryView)).To(Succeed()) diff --git a/tests/utils/context.go b/tests/utils/context.go index 624ae7ab..ea02098e 100644 --- a/tests/utils/context.go +++ b/tests/utils/context.go @@ -14,14 +14,13 @@ import ( "go.uber.org/mock/gomock" "gopkg.in/yaml.v3" - "github.com/flowexec/flow/internal/cache" - cacheMocks "github.com/flowexec/flow/internal/cache/mocks" - "github.com/flowexec/flow/internal/context" - "github.com/flowexec/flow/internal/filesystem" - "github.com/flowexec/flow/internal/io" - "github.com/flowexec/flow/internal/logger" "github.com/flowexec/flow/internal/runner/mocks" "github.com/flowexec/flow/internal/services/store" + "github.com/flowexec/flow/pkg/cache" + cacheMocks "github.com/flowexec/flow/pkg/cache/mocks" + "github.com/flowexec/flow/pkg/context" + "github.com/flowexec/flow/pkg/filesystem" + "github.com/flowexec/flow/pkg/logger" "github.com/flowexec/flow/tests/utils/builder" "github.com/flowexec/flow/types/config" "github.com/flowexec/flow/types/workspace" @@ -54,7 +53,7 @@ func NewContext(ctx stdCtx.Context, tb testing.TB) *Context { stdOut, stdIn := createTempIOFiles(tb) tempLogger := tuikitIO.NewLogger( tuikitIO.WithOutput(stdOut), - tuikitIO.WithTheme(io.Theme("")), + tuikitIO.WithTheme(logger.Theme("")), tuikitIO.WithMode(tuikitIO.Text), tuikitIO.WithExitFunc(func(msg string, args ...any) { msg = fmt.Sprintf(msg, args...) @@ -126,7 +125,7 @@ func ResetTestContext(ctx *Context, tb testing.TB) { setTestEnv(tb, ctx.configDir, ctx.cacheDir) newLogger := tuikitIO.NewLogger( tuikitIO.WithOutput(stdOut), - tuikitIO.WithTheme(io.Theme("")), + tuikitIO.WithTheme(logger.Theme("")), tuikitIO.WithMode(tuikitIO.Text), tuikitIO.WithExitFunc(func(msg string, args ...any) { msg = fmt.Sprintf(msg, args...) diff --git a/tests/utils/runner.go b/tests/utils/runner.go index 34783ea9..c532767b 100644 --- a/tests/utils/runner.go +++ b/tests/utils/runner.go @@ -6,7 +6,8 @@ import ( "os/exec" "github.com/flowexec/flow/cmd" - "github.com/flowexec/flow/internal/context" + "github.com/flowexec/flow/pkg/cli" + "github.com/flowexec/flow/pkg/context" ) type CommandRunner struct{} @@ -21,7 +22,8 @@ func (r *CommandRunner) Run(ctx *context.Context, args ...string) (err error) { err = fmt.Errorf("panic occurred: %v", r) } }() - rootCmd := cmd.NewRootCmd(ctx) + rootCmd := cli.BuildRootCommand(ctx) + cli.RegisterAllCommands(ctx, rootCmd) rootCmd.SetArgs(args) rootCmd.SetIn(ctx.StdIn()) rootCmd.SetOut(ctx.StdOut()) diff --git a/tools/docsgen/main.go b/tools/docsgen/main.go index 64d15327..3391e3f1 100644 --- a/tools/docsgen/main.go +++ b/tools/docsgen/main.go @@ -9,8 +9,8 @@ import ( "github.com/spf13/cobra/doc" - "github.com/flowexec/flow/cmd" - "github.com/flowexec/flow/internal/context" + "github.com/flowexec/flow/pkg/cli" + "github.com/flowexec/flow/pkg/context" ) const ( @@ -24,8 +24,8 @@ func main() { ctx := context.NewContext(bkgCtx, cancelFunc, os.Stdin, os.Stdout) defer ctx.Finalize() - rootCmd := cmd.NewRootCmd(ctx) - cmd.RegisterSubCommands(ctx, rootCmd) + rootCmd := cli.BuildRootCommand(ctx) + cli.RegisterAllCommands(ctx, rootCmd) rootCmd.DisableAutoGenTag = true if err := doc.GenMarkdownTree(rootCmd, filepath.Join(rootDir(), DocsDir, cliDir)); err != nil { panic(err) diff --git a/types/executable/executable.go b/types/executable/executable.go index a4dcf589..d154f5e5 100644 --- a/types/executable/executable.go +++ b/types/executable/executable.go @@ -12,8 +12,8 @@ import ( "github.com/flowexec/tuikit/types" "gopkg.in/yaml.v3" - "github.com/flowexec/flow/internal/errors" "github.com/flowexec/flow/internal/utils" + "github.com/flowexec/flow/pkg/errors" "github.com/flowexec/flow/types/common" ) @@ -391,7 +391,7 @@ func (l ExecutableList) FindByVerbAndID(verb Verb, id string) (*Executable, erro if exec != nil { return exec, nil } - return nil, errors.ExecutableNotFoundError{Verb: string(verb), Name: name} + return nil, errors.NewExecutableNotFoundError(NewRef(name, verb).String()) } func (l ExecutableList) FilterByTags(tags common.Tags) ExecutableList {