Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/smoke-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
run: shopware-cli extension package plugin --disable-git --release

- name: Validate Plugin
run: shopware-cli extension validate FroshTools.zip --only sw-cli
run: shopware-cli extension validate FroshTools.zip --only builtin

- name: Get Changelog
run: shopware-cli extension get-changelog FroshTools.zip
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ shopware-cli project storefront-watch

The verifier registers tools through the name-only `Tool` interface. `CheckTool`, `FixTool`, and `FormatTool` add capabilities; commands select the relevant capability before applying `--only` or `--exclude`. An unsupported tool name is an error, and `ToolList[T]` preserves the capability type through filtering.

- **Checkers**: `sw-cli`, PHPStan, ESLint, Stylelint, Storefront Twig
- **Checkers**: `builtin` (legacy alias: `sw-cli`), PHPStan, ESLint, Stylelint, Storefront Twig
- **Fixers**: Rector, ESLint, Stylelint, Symfony XML conversion
- **Formatters**: PHP-CS-Fixer, Prettier

Expand Down
2 changes: 1 addition & 1 deletion architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ type FormatTool interface {

Registration is `func init() { AddTool(PhpStan{}) }` into a global `availableTools`. Consumers call `GetToolsOf[CheckTool]()` (or `FixTool` / `FormatTool`) to get a typed `ToolList[T]`; its `Only` and `Exclude` methods preserve that capability type.

Current checkers are `sw-cli`, `phpstan`, `eslint`, `stylelint`, and `storefront-twig`. Fixers are `rector`, `eslint`, `stylelint`, and `symfony-xml`; formatters are `php-cs-fixer`, and `prettier`. `sw-cli` enforces built-in extension rules; it does not validate per-extension metadata in a project context. There is no separate Composer verifier tool.
Current checkers are `builtin`, `phpstan`, `eslint`, `stylelint`, and `storefront-twig`. Fixers are `rector`, `eslint`, `stylelint`, and `symfony-xml`; formatters are `php-cs-fixer`, and `prettier`. `builtin` enforces built-in extension rules; the legacy name `sw-cli` is still accepted as an input alias. It does not validate per-extension metadata in a project context. There is no separate Composer verifier tool.

Extension commands report selected tools as `invoked` and others as `skipped`. This describes selection and invocation, not whether a tool found applicable files or changed them.

Expand Down
1 change: 1 addition & 0 deletions cmd/extension/extension_fix.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var extensionFixCmd = &cobra.Command{
allTools := verifier.GetToolsOf[verifier.FixTool]()
only, _ := cmd.Flags().GetString("only")
exclude, _ := cmd.Flags().GetString("exclude")
verifier.WarnOnDeprecatedToolName(cmd.Context(), only, exclude)

requestedTools, err := allTools.Only(only)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions cmd/extension/extension_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var extensionFormat = &cobra.Command{
allTools := verifier.GetToolsOf[verifier.FormatTool]()
only, _ := cmd.Flags().GetString("only")
exclude, _ := cmd.Flags().GetString("exclude")
verifier.WarnOnDeprecatedToolName(cmd.Context(), only, exclude)

requestedTools, err := allTools.Only(only)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion cmd/extension/extension_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var extensionValidateCmd = &cobra.Command{
only, _ := cmd.Flags().GetString("only")
exclude, _ := cmd.Flags().GetString("exclude")
noCopy, _ := cmd.Flags().GetBool("no-copy")
verifier.WarnOnDeprecatedToolName(cmd.Context(), only, exclude)

tools, statuses, err := selectExtensionValidationTools(only, exclude)
if err != nil {
Expand Down Expand Up @@ -183,7 +184,7 @@ func init() {
extensionValidateCmd.PersistentFlags().String("exclude", "", "Exclude specific tools by name (comma-separated, e.g. phpstan,eslint)")
extensionValidateCmd.PersistentFlags().Bool("no-copy", false, "Do not copy extension files to temporary directory")
extensionValidateCmd.MarkFlagsMutuallyExclusive("format", "reporter")
_ = extensionValidateCmd.PersistentFlags().MarkDeprecated("full", "all validation checks now run by default; omit --full; to restore old behaviour use --only sw-cli")
_ = extensionValidateCmd.PersistentFlags().MarkDeprecated("full", "all validation checks now run by default; omit --full; to restore old behaviour use --only builtin")
_ = extensionValidateCmd.PersistentFlags().MarkDeprecated("reporter", "use --format instead")
_ = extensionValidateCmd.PersistentFlags().MarkHidden("reporter")
extensionValidateCmd.PreRunE = func(cmd *cobra.Command, args []string) error {
Expand Down
6 changes: 3 additions & 3 deletions cmd/extension/extension_validate_selection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestExtensionValidationSelection(t *testing.T) {
assert.Equal(t, []string{"phpstan"}, toolNamesForValidation(tools))
assert.True(t, slices.ContainsFunc(tools, requiresToolSetup))
assert.Equal(t, "invoked", toolStatusByName(t, statuses, "phpstan").Status)
assert.Equal(t, "not selected by --only", toolStatusByName(t, statuses, "sw-cli").Reason)
assert.Equal(t, "not selected by --only", toolStatusByName(t, statuses, "builtin").Reason)
})

t.Run("Twig validation needs no external tools", func(t *testing.T) {
Expand All @@ -59,13 +59,13 @@ func TestExtensionValidationSelection(t *testing.T) {
tools, statuses, err := selectExtensionValidationTools("phpstan,sw-cli", "sw-cli")
require.NoError(t, err)
assert.Equal(t, []string{"phpstan"}, toolNamesForValidation(tools))
assert.Equal(t, "excluded by --exclude", toolStatusByName(t, statuses, "sw-cli").Reason)
assert.Equal(t, "excluded by --exclude", toolStatusByName(t, statuses, "builtin").Reason)
})

t.Run("duplicate only values run once", func(t *testing.T) {
tools, _, err := selectExtensionValidationTools("sw-cli,sw-cli", "")
require.NoError(t, err)
assert.Equal(t, []string{"sw-cli"}, toolNamesForValidation(tools))
assert.Equal(t, []string{"builtin"}, toolNamesForValidation(tools))
})

t.Run("unsupported operation lists checkers", func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions cmd/project/project_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var projectValidateCmd = &cobra.Command{
}
only, _ := cmd.Flags().GetString("only")
exclude, _ := cmd.Flags().GetString("exclude")
verifier.WarnOnDeprecatedToolName(cmd.Context(), only, exclude)
noCopy, _ := cmd.Flags().GetBool("no-copy")
localOnly, _ := cmd.Flags().GetBool("local-only")

Expand Down
4 changes: 2 additions & 2 deletions internal/validation/reporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ func TestPrintToolInvocationTableWithOperationTitle(t *testing.T) {

func TestToolInvocationTableFollowsFindings(t *testing.T) {
check := &testCheck{Results: []CheckResult{{Path: "src/file.php", Line: 1, Message: "problem", Severity: SeverityWarning}}}
tools := []ToolInvocationStatus{{Name: "sw-cli", Status: "invoked"}}
tools := []ToolInvocationStatus{{Name: "builtin", Status: "invoked"}}

summary := captureOutput(func() {
assert.NoError(t, DoCheckReport(check, "summary", false, tools...))
Expand All @@ -287,7 +287,7 @@ func TestStructuredReportsKeepMachineOutputAndShowToolStatuses(t *testing.T) {
check := &testCheck{Results: []CheckResult{}}
tools := []ToolInvocationStatus{
{Name: "phpstan", Status: "invoked"},
{Name: "sw-cli", Status: "skipped", Reason: "not selected by --only"},
{Name: "builtin", Status: "skipped", Reason: "not selected by --only"},
}

var gitlabLog string
Expand Down
10 changes: 5 additions & 5 deletions internal/verifier/sw_cli.go → internal/verifier/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"github.com/shopware/shopware-cli/internal/validation"
)

type SWCLI struct{}
type Builtin struct{}

func (s SWCLI) Name() string {
return "sw-cli"
func (s Builtin) Name() string {
return "builtin"
}

func (s SWCLI) Check(ctx context.Context, check *Check, config ToolConfig) error {
func (s Builtin) Check(ctx context.Context, check *Check, config ToolConfig) error {
if config.Extension == nil {
return nil
}
Expand Down Expand Up @@ -45,5 +45,5 @@ func (s SWCLI) Check(ctx context.Context, check *Check, config ToolConfig) error
}

func init() {
AddTool(SWCLI{})
AddTool(Builtin{})
}
4 changes: 2 additions & 2 deletions internal/verifier/source_location_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestZipValidationUsesArchiveRelativePaths(t *testing.T) {

check := NewCheck()
check.SetSourceRoot(ext.GetPath())
require.NoError(t, SWCLI{}.Check(t.Context(), check, ToolConfig{
require.NoError(t, Builtin{}.Check(t.Context(), check, ToolConfig{
Extension: ext,
RootDir: ext.GetPath(),
}))
Expand Down Expand Up @@ -77,7 +77,7 @@ func TestDirectoryValidationUsesExtensionRelativePaths(t *testing.T) {

check := NewCheck()
check.SetSourceRoot(pluginDir)
require.NoError(t, SWCLI{}.Check(t.Context(), check, ToolConfig{
require.NoError(t, Builtin{}.Check(t.Context(), check, ToolConfig{
Extension: ext,
RootDir: pluginDir,
InputWasDirectory: true,
Expand Down
25 changes: 23 additions & 2 deletions internal/verifier/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/shopware/shopware-cli/internal/extension"
"github.com/shopware/shopware-cli/internal/validation"
"github.com/shopware/shopware-cli/logging"
)

type ToolList[T Tool] []T
Expand Down Expand Up @@ -78,6 +79,26 @@ type FormatTool interface {
Format(ctx context.Context, config ToolConfig, dryRun bool) error
}

func canonicalToolName(name string) string {
if name == "sw-cli" {
return "builtin"
}

return name
}

// WarnOnDeprecatedToolName reports use of the legacy built-in checker name.
func WarnOnDeprecatedToolName(ctx context.Context, values ...string) {
for _, value := range values {
for _, name := range strings.Split(value, ",") {
if strings.TrimSpace(name) == "sw-cli" {
logging.FromContext(ctx).Warnf("The tool name %q is deprecated as input; use %q instead", "sw-cli", "builtin")
return
}
}
}
}

func (tl ToolList[T]) Only(only string) (ToolList[T], error) {
if only == "" {
return tl, nil
Expand All @@ -88,7 +109,7 @@ func (tl ToolList[T]) Only(only string) (ToolList[T], error) {
seen := make(map[string]bool, len(requestedTools))

for _, requestedTool := range requestedTools {
requestedTool = strings.TrimSpace(requestedTool)
requestedTool = canonicalToolName(strings.TrimSpace(requestedTool))
found := false

for _, t := range tl {
Expand Down Expand Up @@ -119,7 +140,7 @@ func (tl ToolList[T]) Exclude(exclude string) (ToolList[T], error) {

names := strings.Split(exclude, ",")
for i, name := range names {
name = strings.TrimSpace(name)
name = canonicalToolName(strings.TrimSpace(name))
names[i] = name
if name == "" {
continue
Expand Down
49 changes: 40 additions & 9 deletions internal/verifier/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"

"github.com/shopware/shopware-cli/logging"
)

type testTool struct{ name string }
Expand All @@ -23,14 +27,14 @@ func toolNames[T Tool](list ToolList[T]) []string {
}

func TestToolsByCapability(t *testing.T) {
assert.ElementsMatch(t, []string{"eslint", "phpstan", "storefront-twig", "stylelint", "sw-cli"}, toolNames(GetToolsOf[CheckTool]()))
assert.ElementsMatch(t, []string{"eslint", "phpstan", "storefront-twig", "stylelint", "builtin"}, toolNames(GetToolsOf[CheckTool]()))
assert.ElementsMatch(t, []string{"eslint", "rector", "stylelint", "symfony-xml"}, toolNames(GetToolsOf[FixTool]()))
assert.ElementsMatch(t, []string{"php-cs-fixer", "prettier"}, toolNames(GetToolsOf[FormatTool]()))
}

func TestOnly_DeduplicatesAndPreservesOrder(t *testing.T) {
t.Parallel()
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"sw-cli"}}
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"builtin"}}
res, err := base.Only("eslint, phpstan,eslint")
assert.NoError(t, err)
assert.Equal(t, []string{"eslint", "phpstan"}, toolNames(res))
Expand All @@ -41,26 +45,26 @@ func TestOnly_DeduplicatesAndPreservesOrder(t *testing.T) {

func TestExclude_EmptyString_NoChange(t *testing.T) {
t.Parallel()
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"sw-cli"}}
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"builtin"}}
res, err := base.Exclude("")
assert.NoError(t, err)
assert.Equal(t, toolNames(base), toolNames(res))
}

func TestExclude_SingleTool(t *testing.T) {
t.Parallel()
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"sw-cli"}}
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"builtin"}}
res, err := base.Exclude("eslint")
assert.NoError(t, err)
assert.Equal(t, []string{"phpstan", "sw-cli"}, toolNames(res))
assert.Equal(t, []string{"phpstan", "builtin"}, toolNames(res))
}

func TestExclude_MultipleTools(t *testing.T) {
t.Parallel()
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"sw-cli"}, testTool{"stylelint"}}
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"builtin"}, testTool{"stylelint"}}
res, err := base.Exclude("eslint, stylelint")
assert.NoError(t, err)
assert.Equal(t, []string{"phpstan", "sw-cli"}, toolNames(res))
assert.Equal(t, []string{"phpstan", "builtin"}, toolNames(res))
}

func TestExclude_AllTools_ReturnsEmpty(t *testing.T) {
Expand All @@ -81,8 +85,35 @@ func TestExclude_UnknownTool_Error(t *testing.T) {

func TestExclude_TrimsAndIgnoresDuplicates(t *testing.T) {
t.Parallel()
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"sw-cli"}}
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"eslint"}, testTool{"builtin"}}
res, err := base.Exclude(" , eslint , eslint , \teslint\t , ")
assert.NoError(t, err)
assert.Equal(t, []string{"phpstan", "sw-cli"}, toolNames(res))
assert.Equal(t, []string{"phpstan", "builtin"}, toolNames(res))
}

func TestOnly_LegacyBuiltinAlias(t *testing.T) {
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"builtin"}}
res, err := base.Only("sw-cli")
assert.NoError(t, err)
assert.Equal(t, []string{"builtin"}, toolNames(res))
}

func TestExclude_LegacyBuiltinAlias(t *testing.T) {
base := ToolList[testTool]{testTool{"phpstan"}, testTool{"builtin"}}
res, err := base.Exclude("sw-cli")
assert.NoError(t, err)
assert.Equal(t, []string{"phpstan"}, toolNames(res))
}

func TestWarnOnDeprecatedToolName(t *testing.T) {
core, logs := observer.New(zap.WarnLevel)
ctx := logging.WithLogger(t.Context(), zap.New(core).Sugar())

WarnOnDeprecatedToolName(ctx, "phpstan,builtin", "sw-cli")

if assert.Len(t, logs.All(), 1) {
assert.Contains(t, logs.All()[0].Message, "sw-cli")
assert.Contains(t, logs.All()[0].Message, "builtin")
assert.Equal(t, "warn", logs.All()[0].Level.String())
}
}
8 changes: 4 additions & 4 deletions skills/shopware-cli-extension-store/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ Run both validations from the extension root and capture the full output **and**

```bash
shopware-cli --version
shopware-cli extension validate . --only sw-cli --format markdown
shopware-cli extension validate . --only sw-cli --store-compliance --format markdown
shopware-cli extension validate . --only builtin --format markdown
shopware-cli extension validate . --only builtin --store-compliance --format markdown
```

- The **exit code** is the pass/fail signal (`0` = pass, non-zero = findings). The report goes to stdout; a usage block or error goes to stderr — do not read a validation failure as a usage error.
- `--format markdown` gives a stable, quotable form. `--reporter` is a deprecated alias that prints a warning — use `--format`.
- Treat the store-compliance run as a delta over the normal run: report only the lines it adds.
- `extension validate` now runs all checkers by default. The two commands above explicitly select only the built-in `sw-cli` checker, not PHPStan/ESLint/Stylelint. (`sw-cli` is that checker's name, not shorthand for the binary.) Report "the `sw-cli` checks passed", not "full validation passed". Source: `cmd/extension/extension_validate.go`, `selectExtensionValidationTools`.
- `extension validate` now runs all checkers by default. The two commands above explicitly select only the built-in `builtin` checker, not PHPStan/ESLint/Stylelint. (`sw-cli` remains accepted as a legacy alias. It is that checker's deprecated name, not shorthand for the binary.) Report "the `builtin` checks passed", not "full validation passed". Source: `cmd/extension/extension_validate.go`, `selectExtensionValidationTools`.
- The Markdown report includes a checker table. `invoked` means the checker was called; it does not prove that files were analyzed or that a check passed. `skipped` means it was not selected or was excluded. Classify only finding lines, not checker-status lines.
- Use one `shopware-cli` binary throughout, and state its version. Never mix binaries mid-answer.
- Each error line ends with its result identifier — that identifier is the row's Source, and `L0` catches any line the table does not name explicitly. The CLI currently prints a missing icon twice; count a repeated line once.
Expand Down Expand Up @@ -139,7 +139,7 @@ Preconditions here work like §2's: a page you had no trigger to read produces n

- CLI binary and version
- inspection timestamp
- sw-cli checks: pass/fail + exit code (state that `--only sw-cli` was used)
- builtin checks: pass/fail + exit code (state that `--only builtin` was used)
- store-compliance checks: pass/fail + exit code
- remote Store listing: inspected / not inspected
- files modified: no
Expand Down
2 changes: 1 addition & 1 deletion skills/shopware-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Common flags include:

- `--only <tools>` — run only the named checkers (comma-separated).
- `--exclude <tools>` — remove checkers from the selected set.
- All checkers run by default, including PHPStan, ESLint, and Stylelint. `--full` is deprecated and has no effect. To do a quick validation use `--only sw-cli`
- All checkers run by default, including PHPStan, ESLint, and Stylelint. `--full` is deprecated and has no effect. To do a quick validation use `--only builtin` (`sw-cli` remains accepted as a legacy alias and emits a deprecation warning).
- `--check-against <mode>` — `highest` (default) or `lowest`: which supported Shopware version to check against.
- `--store-compliance` — enable Store-compliance mode while the current CLI supports the flag. Prefer `validation.store_compliance: true` in `.shopware-extension.yml` for persistent Store intent.
- `--format <format>` — choose an output format supported by the current CLI (`--reporter` is a deprecated alias).
Expand Down
Loading