From da4e66f198c1f3005cbf306f0c5ecb4e74806de0 Mon Sep 17 00:00:00 2001 From: Nick Schuch Date: Thu, 16 Oct 2025 13:38:39 +1000 Subject: [PATCH 1/3] Fix and upgrade our aliases --- cmd/skpr/alias/delete/command.go | 2 - cmd/skpr/alias/list/command.go | 2 - cmd/skpr/alias/set/command.go | 2 - cmd/skpr/main.go | 54 +++++++++++++++++-- .../config/user/aliases/aliases_test.go | 6 +-- internal/client/config/user/config.go | 45 ++++++++++++---- internal/client/config/user/config_test.go | 16 +++--- internal/command/alias/delete/command.go | 8 +-- internal/command/alias/list/command.go | 42 ++++++++++----- internal/command/alias/set/command.go | 15 ++++-- internal/command/config/list/command.go | 7 --- 11 files changed, 143 insertions(+), 56 deletions(-) diff --git a/cmd/skpr/alias/delete/command.go b/cmd/skpr/alias/delete/command.go index 589b575..950f865 100644 --- a/cmd/skpr/alias/delete/command.go +++ b/cmd/skpr/alias/delete/command.go @@ -34,7 +34,5 @@ func NewCommand() *cobra.Command { }, } - cmd.Flags().StringVar(&command.Dir, "dir", ".skpr", "The skpr config directory.") - return cmd } diff --git a/cmd/skpr/alias/list/command.go b/cmd/skpr/alias/list/command.go index 485f78d..4243530 100644 --- a/cmd/skpr/alias/list/command.go +++ b/cmd/skpr/alias/list/command.go @@ -30,7 +30,5 @@ func NewCommand() *cobra.Command { }, } - cmd.Flags().StringVar(&command.Dir, "dir", ".skpr", "The skpr config directory.") - return cmd } diff --git a/cmd/skpr/alias/set/command.go b/cmd/skpr/alias/set/command.go index 732d52e..38ae9c1 100644 --- a/cmd/skpr/alias/set/command.go +++ b/cmd/skpr/alias/set/command.go @@ -35,7 +35,5 @@ func NewCommand() *cobra.Command { }, } - cmd.Flags().StringVar(&command.Dir, "dir", ".skpr", "The skpr config directory.") - return cmd } diff --git a/cmd/skpr/main.go b/cmd/skpr/main.go index ffb50a7..e4bd407 100644 --- a/cmd/skpr/main.go +++ b/cmd/skpr/main.go @@ -2,7 +2,9 @@ package main import ( "context" + "fmt" "os" + "os/exec" "github.com/charmbracelet/fang" "github.com/charmbracelet/lipgloss/v2" @@ -11,12 +13,12 @@ import ( "github.com/skpr/cli/cmd/skpr/alias" "github.com/skpr/cli/cmd/skpr/backup" "github.com/skpr/cli/cmd/skpr/config" - "github.com/skpr/cli/cmd/skpr/cron" "github.com/skpr/cli/cmd/skpr/create" + "github.com/skpr/cli/cmd/skpr/cron" "github.com/skpr/cli/cmd/skpr/daemon" deletecmd "github.com/skpr/cli/cmd/skpr/delete" "github.com/skpr/cli/cmd/skpr/deploy" - "github.com/skpr/cli/cmd/skpr/exec" + execcmd "github.com/skpr/cli/cmd/skpr/exec" "github.com/skpr/cli/cmd/skpr/info" "github.com/skpr/cli/cmd/skpr/list" "github.com/skpr/cli/cmd/skpr/login" @@ -29,9 +31,15 @@ import ( "github.com/skpr/cli/cmd/skpr/rsync" "github.com/skpr/cli/cmd/skpr/shell" "github.com/skpr/cli/cmd/skpr/version" + "github.com/skpr/cli/internal/client/config/user" "github.com/skpr/cli/internal/color" ) +const ( + // GroupAliases is the ID for the alias command group. + GroupAliases = "aliases" +) + const cmdExample = ` # Package application into container images. skpr package 0.0.1 @@ -69,7 +77,7 @@ func main() { cmd.AddCommand(daemon.NewCommand()) cmd.AddCommand(deletecmd.NewCommand()) cmd.AddCommand(deploy.NewCommand()) - cmd.AddCommand(exec.NewCommand()) + cmd.AddCommand(execcmd.NewCommand()) cmd.AddCommand(info.NewCommand()) cmd.AddCommand(list.NewCommand()) cmd.AddCommand(login.NewCommand()) @@ -83,6 +91,9 @@ func main() { cmd.AddCommand(version.NewCommand()) cmd.AddCommand(release.NewCommand()) + // Add user set aliases to the root command. + addAliases(cmd) + if err := fang.Execute(context.Background(), cmd, fang.WithColorSchemeFunc(MyColorScheme)); err != nil { os.Exit(1) } @@ -111,3 +122,40 @@ func MyColorScheme(ld lipgloss.LightDarkFunc) fang.ColorScheme { return s } + +// Adds user defined aliases to the root command. +func addAliases(cmd *cobra.Command) { + configFile, err := user.NewConfigFile() + + // Only add aliases if we got an error. + if err == nil { + // Load the aliases. + aliases, err := configFile.ReadAliases() + if err != nil { + fmt.Fprint(os.Stderr, "Failed to load aliases: ", err.Error(), "\n") + return + } + + cmd.AddGroup(&cobra.Group{ + ID: GroupAliases, + Title: "Alias Commands", + }) + + for k, v := range aliases { + cmd.AddCommand(&cobra.Command{ + Use: k, + Short: fmt.Sprintf("Command: %s", v), + DisableFlagsInUseLine: true, + GroupID: GroupAliases, + RunE: func(cmd *cobra.Command, args []string) error { + e := exec.Command("sh", "-c", v) + e.Stdin = os.Stdin + e.Stdout = os.Stdout + e.Stderr = os.Stderr + e.Env = os.Environ() + return e.Run() + }, + }) + } + } +} diff --git a/internal/client/config/user/aliases/aliases_test.go b/internal/client/config/user/aliases/aliases_test.go index f7b74ce..42a0690 100644 --- a/internal/client/config/user/aliases/aliases_test.go +++ b/internal/client/config/user/aliases/aliases_test.go @@ -10,7 +10,7 @@ import ( func TestExpandAliases(t *testing.T) { args := []string{"mp", "dev"} - aliases := command.Aliases{ + aliases := user.Aliases{ "mp": "mysql image pull", } found, newArgs, err := Expand(args, aliases) @@ -20,7 +20,7 @@ func TestExpandAliases(t *testing.T) { } func TestExpandAliasesWithPlaceholder(t *testing.T) { - aliases := command.Aliases{ + aliases := user.Aliases{ "mp": "mysql image pull $1", "fs": "rsync $1:/data/app/sites/default/files $2", } @@ -40,7 +40,7 @@ func TestExpandAliasesWithPlaceholder(t *testing.T) { func TestExpandNoArgs(t *testing.T) { args := []string{} - aliases := command.Aliases{} + aliases := user.Aliases{} found, newArgs, err := Expand(args, aliases) assert.NoError(t, err) assert.Len(t, newArgs, 0) diff --git a/internal/client/config/user/config.go b/internal/client/config/user/config.go index da83220..dc80030 100644 --- a/internal/client/config/user/config.go +++ b/internal/client/config/user/config.go @@ -1,4 +1,4 @@ -package command +package user import ( "fmt" @@ -18,20 +18,37 @@ type Aliases map[string]string // ConfigFile is the config file. type ConfigFile struct { - Filename string + Path string } // NewConfigFile creates a new config file. -func NewConfigFile(filename string) *ConfigFile { - return &ConfigFile{ - Filename: filename, +func NewConfigFile() (*ConfigFile, error) { + configFile := &ConfigFile{} + + homeDir, err := os.UserHomeDir() + if err != nil { + return configFile, err } + + configFile.Path = filepath.Join(homeDir, ".skpr", "config.yml") + + return configFile, nil } // Read reads config from file. func (c *ConfigFile) Read() (Config, error) { var config Config - data, err := os.ReadFile(c.Filename) + + exists, err := c.Exists() + if err != nil { + return config, err + } + + if !exists { + return config, nil + } + + data, err := os.ReadFile(c.Path) if err != nil { return config, fmt.Errorf("failed to read command config: %w", err) } @@ -40,18 +57,21 @@ func (c *ConfigFile) Read() (Config, error) { if err != nil { return config, fmt.Errorf("failed to unmarshal command config: %w", err) } + return config, nil } // Exists checks if the config file exists. func (c *ConfigFile) Exists() (bool, error) { - _, err := os.Lstat(c.Filename) + _, err := os.Lstat(c.Path) if err != nil { if os.IsNotExist(err) { return false, nil } + return false, err } + return true, nil } @@ -62,8 +82,8 @@ func (c *ConfigFile) Write(config Config) error { return fmt.Errorf("failed to marshal command config: %w", err) } - dir := filepath.Dir(c.Filename) - + // Ensure the directory exists. + dir := filepath.Dir(c.Path) if _, err := os.Stat(dir); os.IsNotExist(err) { err = os.Mkdir(dir, 0700) if err != nil { @@ -71,7 +91,7 @@ func (c *ConfigFile) Write(config Config) error { } } - err = os.WriteFile(c.Filename, data, 0600) + err = os.WriteFile(c.Path, data, 0600) if err != nil { return fmt.Errorf("failed to write command config to file: %w", err) } @@ -82,20 +102,25 @@ func (c *ConfigFile) Write(config Config) error { // ReadAliases reads the Aliases from config. func (c *ConfigFile) ReadAliases() (Aliases, error) { aliases := Aliases{} + exists, err := c.Exists() if err != nil { return Aliases{}, err } + if !exists { return aliases, nil } + cfg, err := c.Read() if err != nil { return aliases, err } + // Ensure aliases are not nil. if cfg.Aliases == nil { return aliases, nil } + return cfg.Aliases, nil } diff --git a/internal/client/config/user/config_test.go b/internal/client/config/user/config_test.go index 20d8379..419a55f 100644 --- a/internal/client/config/user/config_test.go +++ b/internal/client/config/user/config_test.go @@ -1,4 +1,4 @@ -package command +package user import ( "path/filepath" @@ -8,15 +8,18 @@ import ( ) func TestReadWriteConfig(t *testing.T) { - filename := filepath.Join(t.TempDir(), ".skpr", "config.yml") - configFile := NewConfigFile(filename) + filePath := filepath.Join(t.TempDir(), ".skpr", "config.yml") + + configFile := ConfigFile{Path: filePath} + config := Config{ Aliases: Aliases{"foo": "bar baz", "whiz": "whang woo"}, } + err := configFile.Write(config) assert.NoError(t, err) - assert.FileExists(t, filename) + assert.FileExists(t, filePath) newCfg, err := configFile.Read() assert.NoError(t, err) @@ -28,8 +31,9 @@ func TestReadWriteConfig(t *testing.T) { } func TestConfigExists(t *testing.T) { - filename := filepath.Join(t.TempDir(), ".skpr", "config.yml") - configFile := NewConfigFile(filename) + filePath := filepath.Join(t.TempDir(), ".skpr", "config.yml") + + configFile := ConfigFile{Path: filePath} exists, err := configFile.Exists() assert.NoError(t, err) assert.False(t, exists) diff --git a/internal/command/alias/delete/command.go b/internal/command/alias/delete/command.go index 5f096d9..3148ab6 100644 --- a/internal/command/alias/delete/command.go +++ b/internal/command/alias/delete/command.go @@ -3,21 +3,21 @@ package delete import ( "context" "fmt" - "path/filepath" cmdconfig "github.com/skpr/cli/internal/client/config/user" ) // Command struct. type Command struct { - Dir string Alias string } // Run the command. func (cmd *Command) Run(ctx context.Context) error { - configPath := filepath.Join(cmd.Dir, "config.yml") - configFile := cmdconfig.NewConfigFile(configPath) + configFile, err := cmdconfig.NewConfigFile() + if err != nil { + return fmt.Errorf("could not get user config file: %w", err) + } exists, err := configFile.Exists() if err != nil { diff --git a/internal/command/alias/list/command.go b/internal/command/alias/list/command.go index 2ca1266..478ad74 100644 --- a/internal/command/alias/list/command.go +++ b/internal/command/alias/list/command.go @@ -3,39 +3,55 @@ package list import ( "fmt" "os" - "path/filepath" cmdconfig "github.com/skpr/cli/internal/client/config/user" + "github.com/skpr/cli/internal/table" ) // Command struct. -type Command struct { - Dir string -} +type Command struct{} // Run the command. func (cmd *Command) Run() error { - configPath := filepath.Join(cmd.Dir, "config.yml") - configFile := cmdconfig.NewConfigFile(configPath) + configFile, err := cmdconfig.NewConfigFile() + if err != nil { + return fmt.Errorf("could not get user config file: %w", err) + } + exists, err := configFile.Exists() if err != nil { return err } + if !exists { fmt.Println("No aliases defined.") return nil } + config, err := configFile.Read() if err != nil { return err } - aliases := config.Aliases - if len(aliases) > 0 { - for k, v := range aliases { - fmt.Fprintf(os.Stdout, "%-6s%s\n", k+":", v) - } + + if len(config.Aliases) == 0 { + fmt.Println("No aliases defined.") return nil } - fmt.Println("No aliases defined.") - return nil + + // Print the table... + header := []string{ + "Name", + "Command", + } + + var rows [][]string + + for name, command := range config.Aliases { + rows = append(rows, []string{ + name, + command, + }) + } + + return table.Print(os.Stdout, header, rows) } diff --git a/internal/command/alias/set/command.go b/internal/command/alias/set/command.go index 3d5368c..f3dd393 100644 --- a/internal/command/alias/set/command.go +++ b/internal/command/alias/set/command.go @@ -2,27 +2,30 @@ package set import ( "fmt" - "path/filepath" cmdconfig "github.com/skpr/cli/internal/client/config/user" ) // Command struct. type Command struct { - Dir string Alias string Expansion string } // Run the command. func (cmd *Command) Run() error { - configPath := filepath.Join(cmd.Dir, "config.yml") - configFile := cmdconfig.NewConfigFile(configPath) + configFile, err := cmdconfig.NewConfigFile() + if err != nil { + return fmt.Errorf("could not get user config file: %w", err) + } + exists, err := configFile.Exists() if err != nil { return err } + var config cmdconfig.Config + if !exists { config = cmdconfig.Config{ Aliases: cmdconfig.Aliases{ @@ -34,16 +37,20 @@ func (cmd *Command) Run() error { if err != nil { return err } + if config.Aliases == nil { config.Aliases = cmdconfig.Aliases{} } + config.Aliases[cmd.Alias] = cmd.Expansion } + err = configFile.Write(config) if err != nil { return err } fmt.Println("Alias set.") + return nil } diff --git a/internal/command/config/list/command.go b/internal/command/config/list/command.go index 57184f3..17db84c 100644 --- a/internal/command/config/list/command.go +++ b/internal/command/config/list/command.go @@ -72,13 +72,6 @@ func sortConfigList(list []*pb.Config) []*pb.Config { return list } -// Row which can be.... -type Row struct { - Key string `header:"key"` - Value string `header:"value"` - Type string `header:"type"` -} - // Print the table... func Print(w io.Writer, list []*pb.Config, environment string, trimLen int, wide bool) error { header := []string{ From 201cffb83ed0e537f67ad4be4595bd849ba420ff Mon Sep 17 00:00:00 2001 From: Nick Schuch Date: Thu, 16 Oct 2025 13:50:25 +1000 Subject: [PATCH 2/3] More fixes --- cmd/skpr/alias/list/command.go | 4 ++-- cmd/skpr/alias/set/command.go | 6 ++++-- cmd/skpr/main.go | 9 ++++++++- internal/client/config/user/aliases/aliases.go | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cmd/skpr/alias/list/command.go b/cmd/skpr/alias/list/command.go index 4243530..3beee1a 100644 --- a/cmd/skpr/alias/list/command.go +++ b/cmd/skpr/alias/list/command.go @@ -10,8 +10,8 @@ var ( cmdLong = `List all aliases.` cmdExample = ` - # List all aliases and specify the skpr config directory. - skpr alias list --dir="/path/to/.skpr"` + # List all aliases. + skpr alias list` ) // NewCommand creates a new cobra.Command for `skpr alias list`. diff --git a/cmd/skpr/alias/set/command.go b/cmd/skpr/alias/set/command.go index 38ae9c1..3816c07 100644 --- a/cmd/skpr/alias/set/command.go +++ b/cmd/skpr/alias/set/command.go @@ -1,6 +1,8 @@ package set import ( + "strings" + "github.com/spf13/cobra" v1set "github.com/skpr/cli/internal/command/alias/set" @@ -23,14 +25,14 @@ func NewCommand() *cobra.Command { cmd := &cobra.Command{ Use: "set ", - Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), + Args: cobra.MinimumNArgs(2), DisableFlagsInUseLine: true, Short: "Set your alias", Long: cmdLong, Example: cmdExample, RunE: func(cmd *cobra.Command, args []string) error { command.Alias = args[0] - command.Expansion = args[1] + command.Expansion = strings.Join(args[1:], " ") return command.Run() }, } diff --git a/cmd/skpr/main.go b/cmd/skpr/main.go index e4bd407..205d3c3 100644 --- a/cmd/skpr/main.go +++ b/cmd/skpr/main.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/exec" + "strings" "github.com/charmbracelet/fang" "github.com/charmbracelet/lipgloss/v2" @@ -136,6 +137,12 @@ func addAliases(cmd *cobra.Command) { return } + binPath, err := os.Executable() + if err != nil { + fmt.Fprint(os.Stderr, "Failed to get skpr executable path: ", err.Error(), "\n") + return + } + cmd.AddGroup(&cobra.Group{ ID: GroupAliases, Title: "Alias Commands", @@ -148,7 +155,7 @@ func addAliases(cmd *cobra.Command) { DisableFlagsInUseLine: true, GroupID: GroupAliases, RunE: func(cmd *cobra.Command, args []string) error { - e := exec.Command("sh", "-c", v) + e := exec.Command(binPath, strings.Split(v, " ")...) e.Stdin = os.Stdin e.Stdout = os.Stdout e.Stderr = os.Stderr diff --git a/internal/client/config/user/aliases/aliases.go b/internal/client/config/user/aliases/aliases.go index aeef248..962cdc6 100644 --- a/internal/client/config/user/aliases/aliases.go +++ b/internal/client/config/user/aliases/aliases.go @@ -9,7 +9,7 @@ import ( ) // Expand expands aliases in the list of args. -func Expand(args []string, aliases command.Aliases) (bool, []string, error) { +func Expand(args []string, aliases user.Aliases) (bool, []string, error) { found := false if len(args) == 0 { return found, args, nil From 672ef4b3266b523ee6963066a3f7acf162fff5ff Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 22 Oct 2025 20:15:35 +1000 Subject: [PATCH 3/3] Return error if failing to read aliases --- cmd/skpr/main.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/skpr/main.go b/cmd/skpr/main.go index 205d3c3..0e3d7fa 100644 --- a/cmd/skpr/main.go +++ b/cmd/skpr/main.go @@ -93,7 +93,11 @@ func main() { cmd.AddCommand(release.NewCommand()) // Add user set aliases to the root command. - addAliases(cmd) + err := addAliases(cmd) + if err != nil { + fmt.Println("Failed to add alias commands:", err) + os.Exit(1) + } if err := fang.Execute(context.Background(), cmd, fang.WithColorSchemeFunc(MyColorScheme)); err != nil { os.Exit(1) @@ -125,7 +129,7 @@ func MyColorScheme(ld lipgloss.LightDarkFunc) fang.ColorScheme { } // Adds user defined aliases to the root command. -func addAliases(cmd *cobra.Command) { +func addAliases(cmd *cobra.Command) error { configFile, err := user.NewConfigFile() // Only add aliases if we got an error. @@ -133,14 +137,12 @@ func addAliases(cmd *cobra.Command) { // Load the aliases. aliases, err := configFile.ReadAliases() if err != nil { - fmt.Fprint(os.Stderr, "Failed to load aliases: ", err.Error(), "\n") - return + return fmt.Errorf("failed to read aliases: %w", err) } binPath, err := os.Executable() if err != nil { - fmt.Fprint(os.Stderr, "Failed to get skpr executable path: ", err.Error(), "\n") - return + return fmt.Errorf("failed to get skpr executable path: %w", err) } cmd.AddGroup(&cobra.Group{ @@ -165,4 +167,6 @@ func addAliases(cmd *cobra.Command) { }) } } + + return nil }