From 36d3ae126141892af51855adb656cf5c7141fd7d Mon Sep 17 00:00:00 2001 From: Samuel K Date: Thu, 10 Sep 2026 03:48:03 +0000 Subject: [PATCH] feat(workspace): add down alias for workspace delete Add 'down' as an alias for 'devsy workspace delete' to match common Docker Compose mental models without changing existing teardown semantics. - Register 'down' in DeleteCmd.Aliases - Extract aliasDown and aliasRm constants to satisfy goconst - Update Long description noting full Devsy teardown and Compose down mapping - Add CLI contract tests for alias registration, command resolution, help text, and shell completion - Update documentation in stop-and-delete-a-workspace.mdx Closes #1211 --- cmd/workspace/delete.go | 11 ++- cmd/workspace/delete_test.go | 84 +++++++++++++++++++ .../stop-and-delete-a-workspace.mdx | 7 ++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 cmd/workspace/delete_test.go diff --git a/cmd/workspace/delete.go b/cmd/workspace/delete.go index 71c997831..91f4fa893 100644 --- a/cmd/workspace/delete.go +++ b/cmd/workspace/delete.go @@ -17,6 +17,11 @@ import ( "github.com/spf13/cobra" ) +const ( + aliasRm = "rm" + aliasDown = "down" +) + // DeleteCmd holds the delete cmd flags. type DeleteCmd struct { *flags.GlobalFlags @@ -30,9 +35,13 @@ func NewDeleteCmd(globalFlags *flags.GlobalFlags) *cobra.Command { } deleteCmd := &cobra.Command{ Use: "delete [flags] [workspace-path|workspace-name]", - Aliases: []string{"rm"}, + Aliases: []string{aliasRm, aliasDown}, Short: "Delete a workspace", Long: `Delete a workspace by path or name. + +Aliases "rm" and "down" perform the same full Devsy workspace teardown. +For Docker Compose workspaces, teardown uses Docker/Podman Compose down. + Use --ignore-not-found to treat a missing workspace as success.`, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd, args) diff --git a/cmd/workspace/delete_test.go b/cmd/workspace/delete_test.go new file mode 100644 index 000000000..6e2b6f672 --- /dev/null +++ b/cmd/workspace/delete_test.go @@ -0,0 +1,84 @@ +package workspace + +import ( + "bytes" + "fmt" + "testing" + + "github.com/devsy-org/devsy/cmd/flags" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDeleteCmd_Aliases(t *testing.T) { + deleteCmd := NewDeleteCmd(&flags.GlobalFlags{}) + + assert.ElementsMatch(t, []string{aliasRm, aliasDown}, deleteCmd.Aliases) +} + +func TestDeleteCmd_Resolution(t *testing.T) { + workspaceCmd := NewWorkspaceCmd(&flags.GlobalFlags{}) + + testCases := []struct { + name string + args []string + }{ + { + name: "canonical delete", + args: []string{"delete"}, + }, + { + name: "rm alias", + args: []string{aliasRm}, + }, + { + name: "down alias", + args: []string{aliasDown}, + }, + { + name: "down alias with positional workspace", + args: []string{aliasDown, "my-workspace"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resolved, remainingArgs, err := workspaceCmd.Find(tc.args) + require.NoError(t, err) + require.NotNil(t, resolved) + assert.Equal(t, "delete", resolved.Name()) + if len(tc.args) > 1 { + assert.Equal(t, tc.args[1:], remainingArgs) + } else { + assert.Empty(t, remainingArgs) + } + }) + } +} + +func TestDeleteCmd_HelpExposesDownAlias(t *testing.T) { + deleteCmd := NewDeleteCmd(&flags.GlobalFlags{}) + + var buf bytes.Buffer + deleteCmd.SetOut(&buf) + err := deleteCmd.Help() + require.NoError(t, err) + + helpOutput := buf.String() + assert.Contains(t, helpOutput, "Aliases:") + assert.Contains(t, helpOutput, aliasDown) + assert.Contains(t, helpOutput, aliasRm) +} + +func TestDeleteCmd_Completion(t *testing.T) { + workspaceCmd := NewWorkspaceCmd(&flags.GlobalFlags{}) + + var buf bytes.Buffer + err := workspaceCmd.GenBashCompletion(&buf) + require.NoError(t, err) + + completionOutput := buf.String() + assert.Contains(t, completionOutput, fmt.Sprintf("%q", aliasDown)) + assert.Contains(t, completionOutput, fmt.Sprintf("%q", aliasRm)) + assert.Contains(t, completionOutput, `"delete"`) +} diff --git a/sites/docs-devsy-sh/content/docs/developing-in-workspaces/stop-and-delete-a-workspace.mdx b/sites/docs-devsy-sh/content/docs/developing-in-workspaces/stop-and-delete-a-workspace.mdx index bfdd937db..32e00d9f0 100644 --- a/sites/docs-devsy-sh/content/docs/developing-in-workspaces/stop-and-delete-a-workspace.mdx +++ b/sites/docs-devsy-sh/content/docs/developing-in-workspaces/stop-and-delete-a-workspace.mdx @@ -49,6 +49,13 @@ Run the following command to delete a workspace: devsy workspace delete my-workspace ``` +`devsy workspace down` (as well as `rm`) is an alias for `devsy workspace delete`. It performs the same full Devsy workspace teardown: +``` +devsy workspace down my-workspace +``` + +For Docker Compose-backed workspaces, teardown uses Compose `down` (with `--remove-volumes` remaining an opt-in flag to remove associated named volumes). Use `devsy workspace stop` when you want to stop a workspace while retaining its configuration and state so it can be started again later. + If deletion fails because the Provider is not reachable anymore or another error has occurred, you can also force delete a workspace via: ``` devsy workspace delete my-workspace --force