Skip to content

Comments

Add swx alias support for shopware-cli#860

Open
Soner (shyim) wants to merge 1 commit intomainfrom
feat/add-swx-alias
Open

Add swx alias support for shopware-cli#860
Soner (shyim) wants to merge 1 commit intomainfrom
feat/add-swx-alias

Conversation

@shyim
Copy link
Member

@shyim Soner (shyim) commented Feb 20, 2026

Summary:

  • map swx invocation to project console while keeping completion commands intact and ensure rootCmd.Use matches the actual binary name
  • add unit tests covering alias argument mapping and command name detection for Windows/Unix paths
  • update goreleaser configs (AUR, Nix, nfpm, Homebrew cask) to symlink shopware-cli to swx and ensure completions/install hooks adjust accordingly
image image image image

Copilot AI review requested due to automatic review settings February 20, 2026 09:18
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for a swx alias (symlink) to shopware-cli that automatically forwards all invocations to project console, effectively making swx behave as a shorthand for running Shopware console commands. The implementation maintains compatibility with shell completion systems while simplifying common console operations.

Changes:

  • Added alias detection and argument mapping logic in cmd/root.go to intercept swx invocations and forward them to project console
  • Added comprehensive unit tests covering various swx usage scenarios including completions
  • Updated package manager configurations (AUR, Nix, nfpm, Homebrew) to create symbolic links from shopware-cli to swx

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
cmd/root.go Implements swx alias detection, argument mapping logic, and dynamic command naming based on invocation path
cmd/root_test.go Adds unit tests for alias argument mapping and command name detection across Unix/Windows paths
.goreleaser.yaml Configures package managers to create swx symlinks and handles installation/uninstallation hooks
Comments suppressed due to low confidence (1)

.goreleaser.yaml:242

  • When completions are generated for the swx command (e.g., via "swx completion bash"), the AUR, Nix, nfpm, and Homebrew packages only install shopware-cli completions, not swx completions. Users who want swx completions will need to generate them manually by running "swx completion bash/zsh/fish". Consider either documenting this in the package install hooks/readme, or automatically generating and installing both shopware-cli and swx completion files during the build process.
      ln -sf "shopware-cli" "${pkgdir}/usr/bin/swx"

      # license
      install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/shopware-cli/LICENSE"

      # completions
      mkdir -p "${pkgdir}/usr/share/bash-completion/completions/"
      mkdir -p "${pkgdir}/usr/share/zsh/site-functions/"
      mkdir -p "${pkgdir}/usr/share/fish/vendor_completions.d/"
      install -Dm644 "./completions/shopware-cli.bash" "${pkgdir}/usr/share/bash-completion/completions/shopware-cli"
      install -Dm644 "./completions/shopware-cli.zsh" "${pkgdir}/usr/share/zsh/site-functions/_shopware-cli"
      install -Dm644 "./completions/shopware-cli.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/shopware-cli.fish"

nix:
  - name: shopware-cli
    repository:
      owner: FriendsOfShopware
      name: nur-packages
      branch: main
      token: "{{ .Env.NUR_GITHUB_TOKEN }}"
    commit_author:
      name: "Shopware Bot"
      email: github@shopware.com
    post_install: |
      ln -sf "$out/bin/shopware-cli" "$out/bin/swx"
      installShellCompletion --cmd shopware-cli \
      --bash <($out/bin/shopware-cli completion bash) \
      --zsh <($out/bin/shopware-cli completion zsh) \
      --fish <($out/bin/shopware-cli completion fish)
    homepage: "https://sw-cli.fos.gg"
    description: "Command line tool for Shopware 6"
    license: "mit"

nfpms:
  -
    package_name: shopware-cli
    vendor: "shopware AG"
    homepage: https://github.com/shopware/shopware-cli/
    maintainer: Soner Sayakci <s.sayakci@shopware.com>
    description: A cli which contains handy helpful commands for daily Shopware tasks
    license: MIT
    contents:
      - src: /usr/bin/shopware-cli
        dst: /usr/bin/swx
        type: symlink
      - src: ./completions/shopware-cli.bash
        dst: /etc/bash_completion.d/shopware-cli
      - src: ./completions/shopware-cli.fish
        dst: /usr/share/fish/completions/shopware-cli.fish
      - src: ./completions/shopware-cli.zsh
        dst: /usr/local/share/zsh/site-functions/_shopware-cli
    formats:
      - apk
      - deb
      - rpm

homebrew_casks:
  - name: shopware-cli
    repository:
      owner: shopware
      name: homebrew-tap
      token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN_SHOPWARE }}"
    commit_author:
      name: Frosh Automation
      email: ci@fos.gg
    homepage: https://shopware.com
    description: Shopware CLI helps Shopware developers manage extensions
    license: MIT
    hooks:
      post:
        install: |
          system_command "/bin/ln", args: ["-sf", "#{HOMEBREW_PREFIX}/bin/shopware-cli", "#{HOMEBREW_PREFIX}/bin/swx"]
        uninstall: |
          system_command "/bin/rm", args: ["-f", "#{HOMEBREW_PREFIX}/bin/swx"]
    completions:
      bash: completions/shopware-cli.bash
      zsh: completions/shopware-cli.zsh
      fish: completions/shopware-cli.fish

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +25 to +30
func TestMapAliasArgs_SwxAliasWithoutArgs(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx"})

assert.Equal(t, []string{"project", "console"}, args)
}

Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test expects swx without arguments to map to ["project", "console"], but the project console command requires at least one argument according to cobra.MinimumNArgs(1) in cmd/project/project_console.go line 21. When users run swx without any arguments, they will encounter a validation error from Cobra stating that at least 1 argument is required. Consider either removing this test case or modifying the project console command to allow zero arguments (which would run bin/console without arguments, showing available commands).

Suggested change
func TestMapAliasArgs_SwxAliasWithoutArgs(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx"})
assert.Equal(t, []string{"project", "console"}, args)
}

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +80
func mapAliasArgs(argv []string) []string {
if len(argv) == 0 {
return nil
}

args := argv[1:]
if !isSwxAlias(argv[0]) {
return args
}

if len(args) > 0 {
// Let users generate completion scripts for `swx` itself.
if args[0] == "completion" {
return args
}

// Cobra shell completion calls these internal commands.
// Prefixing `project console` preserves swx-as-console behavior for completions.
if args[0] == "__complete" || args[0] == "__completeNoDesc" {
aliasedCompletionArgs := make([]string, 0, len(args)+2)
aliasedCompletionArgs = append(aliasedCompletionArgs, args[0], "project", "console")
aliasedCompletionArgs = append(aliasedCompletionArgs, args[1:]...)

return aliasedCompletionArgs
}
}

// When invoked via the `swx` symlink, forward everything to `project console`.
aliasedArgs := make([]string, 0, len(args)+2)
aliasedArgs = append(aliasedArgs, "project", "console")
aliasedArgs = append(aliasedArgs, args...)

return aliasedArgs
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When users run swx --version or swx --help, these flags are forwarded to project console --version and project console --help, which will show the Symfony console version/help instead of the shopware-cli/swx version/help. Consider adding special handling for these common flags before the general forwarding logic, similar to how "completion" is handled, to ensure users can access swx's own version and help information.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +59
func TestMapAliasArgs_NoArgs(t *testing.T) {
assert.Equal(t, []string{}, mapAliasArgs([]string{"shopware-cli"}))
}

func TestMapAliasArgs_RegularBinary(t *testing.T) {
args := mapAliasArgs([]string{"shopware-cli", "project", "console", "debug:router"})

assert.Equal(t, []string{"project", "console", "debug:router"}, args)
}

func TestMapAliasArgs_SwxAlias(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx", "debug:router", "--env=prod"})

assert.Equal(t, []string{"project", "console", "debug:router", "--env=prod"}, args)
}

func TestMapAliasArgs_SwxAliasWithoutArgs(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx"})

assert.Equal(t, []string{"project", "console"}, args)
}

func TestMapAliasArgs_SwxExeAlias(t *testing.T) {
args := mapAliasArgs([]string{"C:\\tools\\swx.exe", "cache:clear"})

assert.Equal(t, []string{"project", "console", "cache:clear"}, args)
}

func TestMapAliasArgs_SwxCompletion(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx", "completion", "bash"})

assert.Equal(t, []string{"completion", "bash"}, args)
}

func TestMapAliasArgs_SwxInternalCompletion(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx", "__complete", "cache:clear"})

assert.Equal(t, []string{"__complete", "project", "console", "cache:clear"}, args)
}

func TestMapAliasArgs_SwxInternalCompletionNoDesc(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx", "__completeNoDesc", "cache:clear"})

assert.Equal(t, []string{"__completeNoDesc", "project", "console", "cache:clear"}, args)
}

func TestCommandNameFromArgs(t *testing.T) {
assert.Equal(t, "shopware-cli", commandNameFromArgs([]string{"/usr/local/bin/shopware-cli"}))
assert.Equal(t, "swx", commandNameFromArgs([]string{"C:\\tools\\swx.exe"}))
assert.Equal(t, "shopware-cli", commandNameFromArgs(nil))
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test functions in this codebase consistently use t.Parallel() to enable parallel execution (see examples in cmd/project/project_create_test.go, internal/changelog/changelog_test.go, and many other test files). Consider adding t.Parallel() to all test functions in this file to maintain consistency with established testing practices.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +59
package cmd

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestMapAliasArgs_NoArgs(t *testing.T) {
assert.Equal(t, []string{}, mapAliasArgs([]string{"shopware-cli"}))
}

func TestMapAliasArgs_RegularBinary(t *testing.T) {
args := mapAliasArgs([]string{"shopware-cli", "project", "console", "debug:router"})

assert.Equal(t, []string{"project", "console", "debug:router"}, args)
}

func TestMapAliasArgs_SwxAlias(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx", "debug:router", "--env=prod"})

assert.Equal(t, []string{"project", "console", "debug:router", "--env=prod"}, args)
}

func TestMapAliasArgs_SwxAliasWithoutArgs(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx"})

assert.Equal(t, []string{"project", "console"}, args)
}

func TestMapAliasArgs_SwxExeAlias(t *testing.T) {
args := mapAliasArgs([]string{"C:\\tools\\swx.exe", "cache:clear"})

assert.Equal(t, []string{"project", "console", "cache:clear"}, args)
}

func TestMapAliasArgs_SwxCompletion(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx", "completion", "bash"})

assert.Equal(t, []string{"completion", "bash"}, args)
}

func TestMapAliasArgs_SwxInternalCompletion(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx", "__complete", "cache:clear"})

assert.Equal(t, []string{"__complete", "project", "console", "cache:clear"}, args)
}

func TestMapAliasArgs_SwxInternalCompletionNoDesc(t *testing.T) {
args := mapAliasArgs([]string{"/usr/local/bin/swx", "__completeNoDesc", "cache:clear"})

assert.Equal(t, []string{"__completeNoDesc", "project", "console", "cache:clear"}, args)
}

func TestCommandNameFromArgs(t *testing.T) {
assert.Equal(t, "shopware-cli", commandNameFromArgs([]string{"/usr/local/bin/shopware-cli"}))
assert.Equal(t, "swx", commandNameFromArgs([]string{"C:\\tools\\swx.exe"}))
assert.Equal(t, "shopware-cli", commandNameFromArgs(nil))
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding test cases for edge cases such as: swx invoked with version/help flags (e.g., "swx --version", "swx --help"), case-insensitive matching (e.g., "SWX.exe"), and swx with other root-level commands that might need special handling. These would help ensure the alias mapping is robust across different usage scenarios.

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +59
func TestCommandNameFromArgs(t *testing.T) {
assert.Equal(t, "shopware-cli", commandNameFromArgs([]string{"/usr/local/bin/shopware-cli"}))
assert.Equal(t, "swx", commandNameFromArgs([]string{"C:\\tools\\swx.exe"}))
assert.Equal(t, "shopware-cli", commandNameFromArgs(nil))
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isSwxAlias function uses strings.EqualFold for case-insensitive comparison, but the test only verifies exact case matches ("swx") and Windows ".exe" extension. Consider adding a test case for "SWX" or "Swx" to explicitly verify that case-insensitive matching works correctly across platforms, especially since Windows is often case-insensitive for file names.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant