Conversation
There was a problem hiding this comment.
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
swxinvocations and forward them toproject 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-clitoswx
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.
| func TestMapAliasArgs_SwxAliasWithoutArgs(t *testing.T) { | ||
| args := mapAliasArgs([]string{"/usr/local/bin/swx"}) | ||
|
|
||
| assert.Equal(t, []string{"project", "console"}, args) | ||
| } | ||
|
|
There was a problem hiding this comment.
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).
| func TestMapAliasArgs_SwxAliasWithoutArgs(t *testing.T) { | |
| args := mapAliasArgs([]string{"/usr/local/bin/swx"}) | |
| assert.Equal(t, []string{"project", "console"}, args) | |
| } |
| 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 | ||
| } |
There was a problem hiding this comment.
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.
| 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)) | ||
| } |
There was a problem hiding this comment.
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.
| 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)) | ||
| } |
There was a problem hiding this comment.
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.
| 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)) | ||
| } |
There was a problem hiding this comment.
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.
Summary:
project consolewhile keeping completion commands intact and ensurerootCmd.Usematches the actual binary nameshopware-clitoswxand ensure completions/install hooks adjust accordingly