diff --git a/pkg/cmd/list.go b/pkg/cmd/list.go new file mode 100644 index 0000000..87fc155 --- /dev/null +++ b/pkg/cmd/list.go @@ -0,0 +1,42 @@ +/********************************************************************** + * Copyright (C) 2026 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +package cmd + +import ( + "github.com/spf13/cobra" +) + +func NewListCmd() *cobra.Command { + // Create the workspace list command + workspaceListCmd := NewWorkspaceListCmd() + + // Create an alias command that delegates to workspace list + cmd := &cobra.Command{ + Use: "list", + Short: workspaceListCmd.Short, + Long: workspaceListCmd.Long, + PreRunE: workspaceListCmd.PreRunE, + RunE: workspaceListCmd.RunE, + } + + // Copy flags from workspace list command + cmd.Flags().AddFlagSet(workspaceListCmd.Flags()) + + return cmd +} diff --git a/pkg/cmd/list_test.go b/pkg/cmd/list_test.go new file mode 100644 index 0000000..43b3b42 --- /dev/null +++ b/pkg/cmd/list_test.go @@ -0,0 +1,42 @@ +/********************************************************************** + * Copyright (C) 2026 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +package cmd + +import ( + "testing" +) + +func TestListCmd(t *testing.T) { + t.Parallel() + + cmd := NewListCmd() + if cmd == nil { + t.Fatal("NewListCmd() returned nil") + } + + if cmd.Use != "list" { + t.Errorf("Expected Use to be 'list', got '%s'", cmd.Use) + } + + // Verify it has the same behavior as workspace list + workspaceListCmd := NewWorkspaceListCmd() + if cmd.Short != workspaceListCmd.Short { + t.Errorf("Expected Short to match workspace list, got '%s'", cmd.Short) + } +} diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 8a5b8d1..2489e88 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -46,6 +46,8 @@ func NewRootCmd() *cobra.Command { // Add subcommands rootCmd.AddCommand(NewVersionCmd()) rootCmd.AddCommand(NewInitCmd()) + rootCmd.AddCommand(NewWorkspaceCmd()) + rootCmd.AddCommand(NewListCmd()) // Global flags rootCmd.PersistentFlags().String("storage", defaultStoragePath, "Directory where kortex-cli will store all its files") diff --git a/pkg/cmd/workspace.go b/pkg/cmd/workspace.go new file mode 100644 index 0000000..3122375 --- /dev/null +++ b/pkg/cmd/workspace.go @@ -0,0 +1,36 @@ +/********************************************************************** + * Copyright (C) 2026 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +package cmd + +import ( + "github.com/spf13/cobra" +) + +func NewWorkspaceCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "workspace", + Short: "Manage workspaces", + Long: "Manage workspaces registered with kortex-cli init", + } + + // Add subcommands + cmd.AddCommand(NewWorkspaceListCmd()) + + return cmd +} diff --git a/pkg/cmd/workspace_list.go b/pkg/cmd/workspace_list.go new file mode 100644 index 0000000..e8baf01 --- /dev/null +++ b/pkg/cmd/workspace_list.go @@ -0,0 +1,89 @@ +/********************************************************************** + * Copyright (C) 2026 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +package cmd + +import ( + "fmt" + + "github.com/kortex-hub/kortex-cli/pkg/instances" + "github.com/spf13/cobra" +) + +// workspaceListCmd contains the configuration for the workspace list command +type workspaceListCmd struct { + manager instances.Manager +} + +// preRun validates the parameters and flags +func (w *workspaceListCmd) preRun(cmd *cobra.Command, args []string) error { + // Get storage directory from global flag + storageDir, err := cmd.Flags().GetString("storage") + if err != nil { + return fmt.Errorf("failed to read --storage flag: %w", err) + } + + // Create manager + manager, err := instances.NewManager(storageDir) + if err != nil { + return fmt.Errorf("failed to create manager: %w", err) + } + w.manager = manager + + return nil +} + +// run executes the workspace list command logic +func (w *workspaceListCmd) run(cmd *cobra.Command, args []string) error { + // Get all instances + instancesList, err := w.manager.List() + if err != nil { + return fmt.Errorf("failed to list instances: %w", err) + } + + // Display the instances + if len(instancesList) == 0 { + cmd.Println("No workspaces registered") + return nil + } + + for _, instance := range instancesList { + cmd.Printf("ID: %s\n", instance.GetID()) + cmd.Printf(" Sources: %s\n", instance.GetSourceDir()) + cmd.Printf(" Configuration: %s\n", instance.GetConfigDir()) + cmd.Println() + } + + return nil +} + +func NewWorkspaceListCmd() *cobra.Command { + c := &workspaceListCmd{} + + cmd := &cobra.Command{ + Use: "list", + Short: "List all registered workspaces", + Long: "List all workspaces registered with kortex-cli init", + PreRunE: c.preRun, + RunE: c.run, + } + + // TODO: Add flags as needed + + return cmd +} diff --git a/pkg/cmd/workspace_list_test.go b/pkg/cmd/workspace_list_test.go new file mode 100644 index 0000000..c6cb11c --- /dev/null +++ b/pkg/cmd/workspace_list_test.go @@ -0,0 +1,227 @@ +/********************************************************************** + * Copyright (C) 2026 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +package cmd + +import ( + "bytes" + "path/filepath" + "strings" + "testing" + + "github.com/kortex-hub/kortex-cli/pkg/instances" +) + +func TestWorkspaceListCmd(t *testing.T) { + t.Parallel() + + cmd := NewWorkspaceListCmd() + if cmd == nil { + t.Fatal("NewWorkspaceListCmd() returned nil") + } + + if cmd.Use != "list" { + t.Errorf("Expected Use to be 'list', got '%s'", cmd.Use) + } +} + +func TestWorkspaceListCmd_PreRun(t *testing.T) { + t.Parallel() + + t.Run("creates manager from storage flag", func(t *testing.T) { + t.Parallel() + + storageDir := t.TempDir() + rootCmd := NewRootCmd() + rootCmd.SetArgs([]string{"workspace", "list", "--storage", storageDir}) + + // Execute to trigger preRun + err := rootCmd.Execute() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + }) +} + +func TestWorkspaceListCmd_E2E(t *testing.T) { + t.Parallel() + + t.Run("shows no workspaces message when empty", func(t *testing.T) { + t.Parallel() + + storageDir := t.TempDir() + rootCmd := NewRootCmd() + rootCmd.SetArgs([]string{"workspace", "list", "--storage", storageDir}) + + var output bytes.Buffer + rootCmd.SetOut(&output) + + err := rootCmd.Execute() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + result := output.String() + if !strings.Contains(result, "No workspaces registered") { + t.Errorf("Expected 'No workspaces registered' message, got: %s", result) + } + }) + + t.Run("lists single workspace", func(t *testing.T) { + t.Parallel() + + storageDir := t.TempDir() + sourcesDir := t.TempDir() + + // Create a workspace first + manager, err := instances.NewManager(storageDir) + if err != nil { + t.Fatalf("Failed to create manager: %v", err) + } + + instance, err := instances.NewInstance(sourcesDir, filepath.Join(sourcesDir, ".kortex")) + if err != nil { + t.Fatalf("Failed to create instance: %v", err) + } + + addedInstance, err := manager.Add(instance) + if err != nil { + t.Fatalf("Failed to add instance: %v", err) + } + + // Now list workspaces + rootCmd := NewRootCmd() + rootCmd.SetArgs([]string{"workspace", "list", "--storage", storageDir}) + + var output bytes.Buffer + rootCmd.SetOut(&output) + + err = rootCmd.Execute() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + result := output.String() + if !strings.Contains(result, addedInstance.GetID()) { + t.Errorf("Expected output to contain ID %s, got: %s", addedInstance.GetID(), result) + } + if !strings.Contains(result, sourcesDir) { + t.Errorf("Expected output to contain sources dir %s, got: %s", sourcesDir, result) + } + }) + + t.Run("lists multiple workspaces", func(t *testing.T) { + t.Parallel() + + storageDir := t.TempDir() + sourcesDir1 := t.TempDir() + sourcesDir2 := t.TempDir() + + // Create two workspaces + manager, err := instances.NewManager(storageDir) + if err != nil { + t.Fatalf("Failed to create manager: %v", err) + } + + instance1, err := instances.NewInstance(sourcesDir1, filepath.Join(sourcesDir1, ".kortex")) + if err != nil { + t.Fatalf("Failed to create instance 1: %v", err) + } + + instance2, err := instances.NewInstance(sourcesDir2, filepath.Join(sourcesDir2, ".kortex")) + if err != nil { + t.Fatalf("Failed to create instance 2: %v", err) + } + + addedInstance1, err := manager.Add(instance1) + if err != nil { + t.Fatalf("Failed to add instance 1: %v", err) + } + + addedInstance2, err := manager.Add(instance2) + if err != nil { + t.Fatalf("Failed to add instance 2: %v", err) + } + + // Now list workspaces + rootCmd := NewRootCmd() + rootCmd.SetArgs([]string{"workspace", "list", "--storage", storageDir}) + + var output bytes.Buffer + rootCmd.SetOut(&output) + + err = rootCmd.Execute() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + result := output.String() + if !strings.Contains(result, addedInstance1.GetID()) { + t.Errorf("Expected output to contain ID %s, got: %s", addedInstance1.GetID(), result) + } + if !strings.Contains(result, addedInstance2.GetID()) { + t.Errorf("Expected output to contain ID %s, got: %s", addedInstance2.GetID(), result) + } + if !strings.Contains(result, sourcesDir1) { + t.Errorf("Expected output to contain sources dir %s, got: %s", sourcesDir1, result) + } + if !strings.Contains(result, sourcesDir2) { + t.Errorf("Expected output to contain sources dir %s, got: %s", sourcesDir2, result) + } + }) + + t.Run("list command alias works", func(t *testing.T) { + t.Parallel() + + storageDir := t.TempDir() + sourcesDir := t.TempDir() + + // Create a workspace + manager, err := instances.NewManager(storageDir) + if err != nil { + t.Fatalf("Failed to create manager: %v", err) + } + + instance, err := instances.NewInstance(sourcesDir, filepath.Join(sourcesDir, ".kortex")) + if err != nil { + t.Fatalf("Failed to create instance: %v", err) + } + + addedInstance, err := manager.Add(instance) + if err != nil { + t.Fatalf("Failed to add instance: %v", err) + } + + // Use the alias command 'list' instead of 'workspace list' + rootCmd := NewRootCmd() + rootCmd.SetArgs([]string{"list", "--storage", storageDir}) + + var output bytes.Buffer + rootCmd.SetOut(&output) + + err = rootCmd.Execute() + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + result := output.String() + if !strings.Contains(result, addedInstance.GetID()) { + t.Errorf("Expected output to contain ID %s, got: %s", addedInstance.GetID(), result) + } + }) +} diff --git a/pkg/cmd/workspace_test.go b/pkg/cmd/workspace_test.go new file mode 100644 index 0000000..27f63b6 --- /dev/null +++ b/pkg/cmd/workspace_test.go @@ -0,0 +1,54 @@ +/********************************************************************** + * Copyright (C) 2026 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + **********************************************************************/ + +package cmd + +import ( + "testing" +) + +func TestWorkspaceCmd(t *testing.T) { + t.Parallel() + + cmd := NewWorkspaceCmd() + if cmd == nil { + t.Fatal("NewWorkspaceCmd() returned nil") + } + + if cmd.Use != "workspace" { + t.Errorf("Expected Use to be 'workspace', got '%s'", cmd.Use) + } + + // Verify list subcommand exists + listCmd := cmd.Commands() + if len(listCmd) == 0 { + t.Fatal("Expected workspace command to have subcommands") + } + + foundList := false + for _, subCmd := range listCmd { + if subCmd.Use == "list" { + foundList = true + break + } + } + + if !foundList { + t.Error("Expected workspace command to have 'list' subcommand") + } +}