From e4cb8a7c3a22cc1793454aa5ed4ad55fc3c2dc84 Mon Sep 17 00:00:00 2001 From: Malte Janz Date: Fri, 25 Sep 2026 16:14:12 +0200 Subject: [PATCH 1/3] feat: add exclude flag to extension format and fix --- cmd/extension/extension_fix.go | 14 ++++++++++++-- cmd/extension/extension_format.go | 14 ++++++++++++-- cmd/extension/extension_tool_invocation.go | 7 +++++-- cmd/extension/extension_tool_invocation_test.go | 17 ++++++++++++++--- cmd/extension/extension_validate.go | 9 ++------- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/cmd/extension/extension_fix.go b/cmd/extension/extension_fix.go index 0dec60fd..5e820e75 100644 --- a/cmd/extension/extension_fix.go +++ b/cmd/extension/extension_fix.go @@ -1,6 +1,7 @@ package extension import ( + "errors" "fmt" "os" "path/filepath" @@ -51,11 +52,19 @@ var extensionFixCmd = &cobra.Command{ allTools := verifier.GetToolsOf[verifier.FixTool]() only, _ := cmd.Flags().GetString("only") + exclude, _ := cmd.Flags().GetString("exclude") - tools, err := allTools.Only(only) + requestedTools, err := allTools.Only(only) if err != nil { return err } + tools, err := requestedTools.Exclude(exclude) + if err != nil { + return err + } + if len(tools) == 0 { + return errors.New("no fixers selected after applying --exclude") + } for _, tool := range tools { gr.Go(func() error { @@ -64,7 +73,7 @@ var extensionFixCmd = &cobra.Command{ } runErr := gr.Wait() - if err := validation.PrintToolInvocationTable(os.Stdout, "Fixers", extensionToolInvocationStatuses(allTools, tools)); err != nil { + if err := validation.PrintToolInvocationTable(os.Stdout, "Fixers", extensionToolInvocationStatuses(allTools, requestedTools, tools)); err != nil { return err } return runErr @@ -74,5 +83,6 @@ var extensionFixCmd = &cobra.Command{ func init() { extensionRootCmd.AddCommand(extensionFixCmd) extensionFixCmd.Flags().String("only", "", "Run only specific fixers by name (comma-separated, e.g. eslint,rector)") + extensionFixCmd.Flags().String("exclude", "", "Exclude fixers after applying --only (comma-separated, e.g. eslint,rector)") extensionFixCmd.Flags().Bool("allow-non-git", false, "Allow running the fix command on non-git repositories") } diff --git a/cmd/extension/extension_format.go b/cmd/extension/extension_format.go index eb437666..68ea7277 100644 --- a/cmd/extension/extension_format.go +++ b/cmd/extension/extension_format.go @@ -1,6 +1,7 @@ package extension import ( + "errors" "fmt" "os" "path/filepath" @@ -45,11 +46,19 @@ var extensionFormat = &cobra.Command{ allTools := verifier.GetToolsOf[verifier.FormatTool]() only, _ := cmd.Flags().GetString("only") + exclude, _ := cmd.Flags().GetString("exclude") - tools, err := allTools.Only(only) + requestedTools, err := allTools.Only(only) if err != nil { return err } + tools, err := requestedTools.Exclude(exclude) + if err != nil { + return err + } + if len(tools) == 0 { + return errors.New("no formatters selected after applying --exclude") + } for _, tool := range tools { gr.Go(func() error { @@ -58,7 +67,7 @@ var extensionFormat = &cobra.Command{ } runErr := gr.Wait() - if err := validation.PrintToolInvocationTable(os.Stdout, "Formatters", extensionToolInvocationStatuses(allTools, tools)); err != nil { + if err := validation.PrintToolInvocationTable(os.Stdout, "Formatters", extensionToolInvocationStatuses(allTools, requestedTools, tools)); err != nil { return err } return runErr @@ -68,5 +77,6 @@ var extensionFormat = &cobra.Command{ func init() { extensionRootCmd.AddCommand(extensionFormat) extensionFormat.Flags().String("only", "", "Run only specific formatters by name (comma-separated, e.g. prettier,php-cs-fixer)") + extensionFormat.Flags().String("exclude", "", "Exclude formatters after applying --only (comma-separated, e.g. prettier,php-cs-fixer)") extensionFormat.Flags().Bool("dry-run", false, "Run in dry run mode") } diff --git a/cmd/extension/extension_tool_invocation.go b/cmd/extension/extension_tool_invocation.go index d8def664..dcdcadb4 100644 --- a/cmd/extension/extension_tool_invocation.go +++ b/cmd/extension/extension_tool_invocation.go @@ -8,13 +8,16 @@ import ( "github.com/shopware/shopware-cli/internal/verifier" ) -func extensionToolInvocationStatuses[T verifier.Tool](all, selected verifier.ToolList[T]) []validation.ToolInvocationStatus { +func extensionToolInvocationStatuses[T verifier.Tool](all, requested, selected verifier.ToolList[T]) []validation.ToolInvocationStatus { statuses := make([]validation.ToolInvocationStatus, 0, len(all)) for _, tool := range all { status := validation.ToolInvocationStatus{Name: tool.Name(), Status: "skipped", Reason: "not selected by --only"} - if slices.ContainsFunc(selected, func(selected T) bool { return selected.Name() == tool.Name() }) { + switch { + case slices.ContainsFunc(selected, func(selected T) bool { return selected.Name() == tool.Name() }): status.Status = "invoked" status.Reason = "" + case slices.ContainsFunc(requested, func(requested T) bool { return requested.Name() == tool.Name() }): + status.Reason = "excluded by --exclude" } statuses = append(statuses, status) } diff --git a/cmd/extension/extension_tool_invocation_test.go b/cmd/extension/extension_tool_invocation_test.go index d5b6474b..b724ef76 100644 --- a/cmd/extension/extension_tool_invocation_test.go +++ b/cmd/extension/extension_tool_invocation_test.go @@ -16,12 +16,23 @@ func TestExtensionToolInvocationStatuses(t *testing.T) { func assertExtensionToolInvocationStatuses[T verifier.Tool](t *testing.T, all verifier.ToolList[T]) { t.Helper() - selected, err := all.Only(all[0].Name()) + requested, err := all.Only(all[0].Name() + "," + all[1].Name()) + require.NoError(t, err) + selected, err := requested.Exclude(all[1].Name()) require.NoError(t, err) - statuses := extensionToolInvocationStatuses(all, selected) + statuses := extensionToolInvocationStatuses(all, requested, selected) assert.Len(t, statuses, len(all)) assert.Equal(t, "invoked", toolStatusByName(t, statuses, all[0].Name()).Status) assert.Equal(t, "skipped", toolStatusByName(t, statuses, all[1].Name()).Status) - assert.Equal(t, "not selected by --only", toolStatusByName(t, statuses, all[1].Name()).Reason) + assert.Equal(t, "excluded by --exclude", toolStatusByName(t, statuses, all[1].Name()).Reason) + assert.Equal(t, "not selected by --only", toolStatusByName(t, statuses, all[2].Name()).Reason) + + _, err = requested.Exclude(all[2].Name()) + require.ErrorContains(t, err, "not found") +} + +func TestExtensionFixAndFormatHaveExcludeFlag(t *testing.T) { + assert.NotNil(t, extensionFixCmd.Flags().Lookup("exclude")) + assert.NotNil(t, extensionFormat.Flags().Lookup("exclude")) } diff --git a/cmd/extension/extension_validate.go b/cmd/extension/extension_validate.go index 348f5db4..9fc4da0c 100644 --- a/cmd/extension/extension_validate.go +++ b/cmd/extension/extension_validate.go @@ -152,14 +152,9 @@ func selectExtensionValidationTools(full bool, only, exclude string) (verifier.T return nil, nil, errors.New("no validation checks selected after applying --exclude") } - statuses := extensionToolInvocationStatuses(validationTools, selected) + statuses := extensionToolInvocationStatuses(validationTools, requestedTools, selected) for i := range statuses { - if statuses[i].Status == "invoked" { - continue - } - if slices.ContainsFunc(requestedTools, func(tool verifier.CheckTool) bool { return tool.Name() == statuses[i].Name }) { - statuses[i].Reason = "excluded by --exclude" - } else if only == "" { + if only == "" && statuses[i].Reason == "not selected by --only" { statuses[i].Reason = "not selected; use --full or --only" } } From 9d62cf446cb1f2d03813ebf779158fad5a488308 Mon Sep 17 00:00:00 2001 From: Lena Forlin <118278183+moshimorschi@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:38:33 +0200 Subject: [PATCH 2/3] fix: check --only and --exclude before tool setup in extension fix and format (#1624) --- cmd/extension/extension_fix.go | 37 ++++++++++++++++--------------- cmd/extension/extension_format.go | 37 ++++++++++++++++--------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/cmd/extension/extension_fix.go b/cmd/extension/extension_fix.go index 5e820e75..68b50b70 100644 --- a/cmd/extension/extension_fix.go +++ b/cmd/extension/extension_fix.go @@ -19,10 +19,23 @@ var extensionFixCmd = &cobra.Command{ Use: "fix path", Short: "Apply code-quality fixes to an extension", Args: cobra.ExactArgs(1), - PreRunE: func(cmd *cobra.Command, args []string) error { - return verifier.SetupTools(cmd.Context(), cmd.Root().Version) - }, RunE: func(cmd *cobra.Command, args []string) error { + allTools := verifier.GetToolsOf[verifier.FixTool]() + only, _ := cmd.Flags().GetString("only") + exclude, _ := cmd.Flags().GetString("exclude") + + requestedTools, err := allTools.Only(only) + if err != nil { + return err + } + tools, err := requestedTools.Exclude(exclude) + if err != nil { + return err + } + if len(tools) == 0 { + return errors.New("no fixers selected after applying --exclude") + } + allowNonGit, _ := cmd.Flags().GetBool("allow-non-git") if !allowNonGit { @@ -48,23 +61,11 @@ var extensionFixCmd = &cobra.Command{ logging.FromContext(cmd.Context()).Debugf("Running fixes for Shopware version: %s", toolCfg.MinShopwareVersion) - var gr errgroup.Group - - allTools := verifier.GetToolsOf[verifier.FixTool]() - only, _ := cmd.Flags().GetString("only") - exclude, _ := cmd.Flags().GetString("exclude") - - requestedTools, err := allTools.Only(only) - if err != nil { + if err := verifier.SetupTools(cmd.Context(), cmd.Root().Version); err != nil { return err } - tools, err := requestedTools.Exclude(exclude) - if err != nil { - return err - } - if len(tools) == 0 { - return errors.New("no fixers selected after applying --exclude") - } + + var gr errgroup.Group for _, tool := range tools { gr.Go(func() error { diff --git a/cmd/extension/extension_format.go b/cmd/extension/extension_format.go index 68ea7277..c89f44ab 100644 --- a/cmd/extension/extension_format.go +++ b/cmd/extension/extension_format.go @@ -19,10 +19,23 @@ var extensionFormat = &cobra.Command{ Use: "format path", Short: "Format an extension's PHP, JavaScript, SCSS, and Administration Twig files", Args: cobra.ExactArgs(1), - PreRunE: func(cmd *cobra.Command, args []string) error { - return verifier.SetupTools(cmd.Context(), cmd.Root().Version) - }, RunE: func(cmd *cobra.Command, args []string) error { + allTools := verifier.GetToolsOf[verifier.FormatTool]() + only, _ := cmd.Flags().GetString("only") + exclude, _ := cmd.Flags().GetString("exclude") + + requestedTools, err := allTools.Only(only) + if err != nil { + return err + } + tools, err := requestedTools.Exclude(exclude) + if err != nil { + return err + } + if len(tools) == 0 { + return errors.New("no formatters selected after applying --exclude") + } + dryRun, _ := cmd.Flags().GetBool("dry-run") path, err := filepath.Abs(args[0]) @@ -42,23 +55,11 @@ var extensionFormat = &cobra.Command{ logging.FromContext(cmd.Context()).Debugf("Running fixes for Shopware version: %s", toolCfg.MinShopwareVersion) - var gr errgroup.Group - - allTools := verifier.GetToolsOf[verifier.FormatTool]() - only, _ := cmd.Flags().GetString("only") - exclude, _ := cmd.Flags().GetString("exclude") - - requestedTools, err := allTools.Only(only) - if err != nil { + if err := verifier.SetupTools(cmd.Context(), cmd.Root().Version); err != nil { return err } - tools, err := requestedTools.Exclude(exclude) - if err != nil { - return err - } - if len(tools) == 0 { - return errors.New("no formatters selected after applying --exclude") - } + + var gr errgroup.Group for _, tool := range tools { gr.Go(func() error { From 32035c01e510e5323681c13204a056728fcb7b22 Mon Sep 17 00:00:00 2001 From: Malte Janz Date: Mon, 28 Sep 2026 14:00:41 +0200 Subject: [PATCH 3/3] fix: toolDirectory and setuptools call ordering for extension fix + format --- cmd/extension/extension_fix.go | 6 +----- cmd/extension/extension_format.go | 6 +----- internal/verifier/extension.go | 7 +++++++ internal/verifier/extension_test.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 internal/verifier/extension_test.go diff --git a/cmd/extension/extension_fix.go b/cmd/extension/extension_fix.go index 68b50b70..fb313062 100644 --- a/cmd/extension/extension_fix.go +++ b/cmd/extension/extension_fix.go @@ -54,17 +54,13 @@ var extensionFixCmd = &cobra.Command{ return err } - toolCfg, err := verifier.ConvertExtensionToToolConfig(ext) + toolCfg, err := verifier.SetupExtensionToolConfig(cmd.Context(), cmd.Root().Version, ext) if err != nil { return err } logging.FromContext(cmd.Context()).Debugf("Running fixes for Shopware version: %s", toolCfg.MinShopwareVersion) - if err := verifier.SetupTools(cmd.Context(), cmd.Root().Version); err != nil { - return err - } - var gr errgroup.Group for _, tool := range tools { diff --git a/cmd/extension/extension_format.go b/cmd/extension/extension_format.go index c89f44ab..50b3cfce 100644 --- a/cmd/extension/extension_format.go +++ b/cmd/extension/extension_format.go @@ -48,17 +48,13 @@ var extensionFormat = &cobra.Command{ return err } - toolCfg, err := verifier.ConvertExtensionToToolConfig(ext) + toolCfg, err := verifier.SetupExtensionToolConfig(cmd.Context(), cmd.Root().Version, ext) if err != nil { return err } logging.FromContext(cmd.Context()).Debugf("Running fixes for Shopware version: %s", toolCfg.MinShopwareVersion) - if err := verifier.SetupTools(cmd.Context(), cmd.Root().Version); err != nil { - return err - } - var gr errgroup.Group for _, tool := range tools { diff --git a/internal/verifier/extension.go b/internal/verifier/extension.go index d24f7ed7..42825185 100644 --- a/internal/verifier/extension.go +++ b/internal/verifier/extension.go @@ -12,6 +12,13 @@ import ( "github.com/shopware/shopware-cli/internal/validation" ) +func SetupExtensionToolConfig(ctx context.Context, version string, ext extension.Extension) (*ToolConfig, error) { + if err := SetupTools(ctx, version); err != nil { + return nil, err + } + return ConvertExtensionToToolConfig(ext) +} + func ConvertExtensionToToolConfig(ext extension.Extension) (*ToolConfig, error) { var ignores []validation.ToolConfigIgnore diff --git a/internal/verifier/extension_test.go b/internal/verifier/extension_test.go new file mode 100644 index 00000000..76218c4f --- /dev/null +++ b/internal/verifier/extension_test.go @@ -0,0 +1,28 @@ +package verifier + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/shopware/shopware-cli/internal/extension" + "github.com/shopware/shopware-cli/internal/testhelper" +) + +func TestSetupExtensionToolConfigUsesConfiguredToolDirectory(t *testing.T) { + stubShopwareVersions(t) + previous := toolDirectory + t.Cleanup(func() { setToolDirectory(previous) }) + setToolDirectory(filepath.Join(t.TempDir(), "stale")) + + toolDir := filepath.Join(t.TempDir(), "tools") + t.Setenv("SHOPWARE_CLI_TOOLS_DIR", toolDir) + ext, err := extension.GetExtensionByFolder(t.Context(), testhelper.NewPlugin(t, "Example")) + require.NoError(t, err) + + cfg, err := SetupExtensionToolConfig(t.Context(), "test", ext) + require.NoError(t, err) + assert.Equal(t, toolDir, cfg.ToolDirectory) +}