From f8418fe8662eb0748a1adb54efb741160dbac052 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Tue, 19 May 2026 11:02:23 +0200 Subject: [PATCH 1/2] fix(devtui): regenerate var/plugins.json before starting watchers The standalone admin-watch / storefront-watch commands run filterAndWritePluginJson before launching the watcher; devtui skipped that step and the watcher could fail with a missing var/plugins.json. Extract the shared pieces (LoadProjectAssetSources, ExcludeExtensionsFromSources, WriteProjectPluginJson, WritePluginJsonForSources) into internal/extension so both code paths share them, then call WriteProjectPluginJson from the devtui watcher start commands. While here: - stop DumpAndLoadAssetSourcesOfProject from inheriting os.Stdout/Stderr so bundle:dump's success line no longer corrupts the TUI; surface captured output only on failure. - run devtui's watcher prep with logging.DisableLogger so the npm "Installing dependencies" log stays out of the rendered UI. - carry the real error on watcherStoppedMsg and append it to the logs tab so startup failures are visible instead of a silent "stopped". --- cmd/project/platform.go | 108 +++++------------- internal/devtui/model.go | 6 +- internal/devtui/model_test.go | 2 +- internal/devtui/model_update.go | 2 +- .../overlay_sales_channel_picker_test.go | 2 +- internal/devtui/tab_general.go | 27 ++++- internal/devtui/tab_general_test.go | 12 +- internal/devtui/tab_logs.go | 8 ++ internal/extension/project.go | 11 +- internal/extension/project_plugin_json.go | 74 ++++++++++++ 10 files changed, 153 insertions(+), 99 deletions(-) create mode 100644 internal/extension/project_plugin_json.go diff --git a/cmd/project/platform.go b/cmd/project/platform.go index 83b50181..c6f5352a 100644 --- a/cmd/project/platform.go +++ b/cmd/project/platform.go @@ -1,12 +1,8 @@ package project import ( - "context" - "encoding/json" "fmt" "os" - "os/exec" - "path" "path/filepath" "slices" "strings" @@ -72,35 +68,7 @@ func filterAndWritePluginJson(cmd *cobra.Command, projectRoot string, shopCfg *s return err } - assetConfig := extension.AssetBuildConfig{ - ShopwareRoot: projectRoot, - Executor: cmdExecutor, - } - - cfgs := extension.BuildAssetConfigFromExtensions(cmd.Context(), sources, assetConfig) - - if _, err := extension.InstallNodeModulesOfConfigs(cmd.Context(), cfgs, assetConfig); err != nil { - return err - } - - // Normalize paths for the execution environment (e.g. Docker container). - for _, cfg := range cfgs { - cfg.BasePath = cmdExecutor.NormalizePath(cfg.BasePath) - for i, v := range cfg.Views { - cfg.Views[i] = cmdExecutor.NormalizePath(v) - } - } - - pluginJson, err := json.MarshalIndent(cfgs, "", " ") - if err != nil { - return err - } - - if err := os.WriteFile(path.Join(projectRoot, "var", "plugins.json"), pluginJson, os.ModePerm); err != nil { - return err - } - - return nil + return extension.WritePluginJsonForSources(cmd.Context(), projectRoot, sources, cmdExecutor) } func filterAndGetSources(cmd *cobra.Command, projectRoot string, shopCfg *shop.Config) ([]asset.Source, error) { @@ -109,9 +77,7 @@ func filterAndGetSources(cmd *cobra.Command, projectRoot string, shopCfg *shop.C return nil, err } - sources, err := extension.DumpAndLoadAssetSourcesOfProject(executor.AllowBinCI(cmd.Context()), projectRoot, shopCfg, func(ctx context.Context, args ...string) *exec.Cmd { - return cmdExecutor.ConsoleCommand(ctx, args...).Cmd - }) + sources, err := extension.LoadProjectAssetSources(cmd.Context(), projectRoot, shopCfg, cmdExecutor) if err != nil { return nil, err } @@ -124,87 +90,67 @@ func filterAndGetSources(cmd *cobra.Command, projectRoot string, shopCfg *shop.C return nil, fmt.Errorf("only-extensions and skip-extensions cannot be used together") } - if onlyCustomStatic { - logging.FromContext(cmd.Context()).Infof("Only including extensions from custom/static-plugins directory") - logging.FromContext(cmd.Context()).Debugf("Found %d total extensions before filtering", len(sources)) + logger := logging.FromContext(cmd.Context()) + + switch { + case onlyCustomStatic: + logger.Infof("Only including extensions from custom/static-plugins directory") + logger.Debugf("Found %d total extensions before filtering", len(sources)) for _, s := range sources { - logging.FromContext(cmd.Context()).Debugf("Extension: %s, Path: %s", s.Name, s.Path) + logger.Debugf("Extension: %s, Path: %s", s.Name, s.Path) } sources = slices.DeleteFunc(sources, func(s asset.Source) bool { - // We want to always include the Storefront extension, otherwise the watchers have problems + // Storefront must stay or the watchers break. if s.Name == storefrontBundleName { return false } - // First try to resolve any symlinks resolvedPath, err := filepath.EvalSymlinks(s.Path) if err != nil { - logging.FromContext(cmd.Context()).Errorf("Failed to resolve symlink for %s: %v", s.Path, err) + logger.Errorf("Failed to resolve symlink for %s: %v", s.Path, err) return true } absPath, err := filepath.Abs(resolvedPath) if err != nil { - logging.FromContext(cmd.Context()).Errorf("Failed to get absolute path for %s: %v", resolvedPath, err) + logger.Errorf("Failed to get absolute path for %s: %v", resolvedPath, err) return true } - logging.FromContext(cmd.Context()).Debugf("Extension %s: Original path: %s, Resolved absolute path: %s", s.Name, s.Path, absPath) + logger.Debugf("Extension %s: Original path: %s, Resolved absolute path: %s", s.Name, s.Path, absPath) customStaticDir := filepath.Join("custom", "static-plugins") - isCustomStatic := strings.Contains(absPath, customStaticDir) || strings.HasSuffix(absPath, customStaticDir) - if !isCustomStatic { - logging.FromContext(cmd.Context()).Debugf("Excluding %s as it's not in custom/static-plugins", s.Name) + logger.Debugf("Excluding %s as it's not in custom/static-plugins", s.Name) } return !isCustomStatic }) - logging.FromContext(cmd.Context()).Debugf("Found %d custom/static extensions after filtering", len(sources)) + logger.Debugf("Found %d custom/static extensions after filtering", len(sources)) for _, s := range sources { - logging.FromContext(cmd.Context()).Debugf("Included extension: %s, Path: %s", s.Name, s.Path) + logger.Debugf("Included extension: %s, Path: %s", s.Name, s.Path) } - logging.FromContext(cmd.Context()).Debugf("Included extensions:") - for _, s := range sources { - logging.FromContext(cmd.Context()).Debugf(" - %s", s.Name) - } - } - - if onlyExtensions == "" && skipExtensions == "" && !onlyCustomStatic { - logging.FromContext(cmd.Context()).Infof("Excluding extensions based on project config: %s", strings.Join(shopCfg.Build.ExcludeExtensions, ", ")) + case onlyExtensions != "": + logger.Infof("Only including extensions: %s", onlyExtensions) + allowed := strings.Split(onlyExtensions, ",") sources = slices.DeleteFunc(sources, func(s asset.Source) bool { - // We want to always include the Storefront extension, otherwise the watchers have problems + // Storefront must stay or the watchers break. if s.Name == storefrontBundleName { return false } - - return slices.Contains(shopCfg.Build.ExcludeExtensions, s.Name) + return !slices.Contains(allowed, s.Name) }) - } - - if onlyExtensions != "" { - logging.FromContext(cmd.Context()).Infof("Only including extensions: %s", onlyExtensions) - sources = slices.DeleteFunc(sources, func(s asset.Source) bool { - // We want to always include the Storefront extension, otherwise the watchers have problems - if s.Name == storefrontBundleName { - return false - } - return !slices.Contains(strings.Split(onlyExtensions, ","), s.Name) - }) - } else if skipExtensions != "" { - logging.FromContext(cmd.Context()).Infof("Excluding extensions: %s", skipExtensions) - sources = slices.DeleteFunc(sources, func(s asset.Source) bool { - // We want to always include the Storefront extension, otherwise the watchers have problems - if s.Name == storefrontBundleName { - return false - } + case skipExtensions != "": + logger.Infof("Excluding extensions: %s", skipExtensions) + sources = extension.ExcludeExtensionsFromSources(sources, strings.Split(skipExtensions, ",")) - return slices.Contains(strings.Split(skipExtensions, ","), s.Name) - }) + default: + logger.Infof("Excluding extensions based on project config: %s", strings.Join(shopCfg.Build.ExcludeExtensions, ", ")) + sources = extension.ExcludeExtensionsFromSources(sources, shopCfg.Build.ExcludeExtensions) } return sources, nil diff --git a/internal/devtui/model.go b/internal/devtui/model.go index da408744..706a7106 100644 --- a/internal/devtui/model.go +++ b/internal/devtui/model.go @@ -109,7 +109,7 @@ func New(opts Options) Model { return Model{ activeTab: tabGeneral, - general: NewGeneralModel(opts.Executor.Type(), shopURL, username, password, opts.ProjectRoot, opts.Executor), + general: NewGeneralModel(opts.Executor.Type(), shopURL, username, password, opts.ProjectRoot, opts.Executor, opts.Config), logs: NewLogsModel(opts.ProjectRoot, isDocker), configTab: NewConfigModel(opts.Config), dockerMode: isDocker, @@ -208,6 +208,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.general.sfWatchRunning = false } delete(m.watchers, msg.name) + if msg.err != nil { + m.logs.AppendErrorLine(msg.name + " failed to start: " + msg.err.Error()) + m.activeTab = tabLogs + } return m, nil case logDoneMsg: diff --git a/internal/devtui/model_test.go b/internal/devtui/model_test.go index 0c2eb12b..e4d8102e 100644 --- a/internal/devtui/model_test.go +++ b/internal/devtui/model_test.go @@ -18,7 +18,7 @@ import ( func newTestModel() Model { return Model{ phase: phaseDashboard, - general: NewGeneralModel("local", "http://localhost:8000", "", "", "/tmp/project", nil), + general: NewGeneralModel("local", "http://localhost:8000", "", "", "/tmp/project", nil, nil), logs: NewLogsModel("/tmp/project", false), configTab: NewConfigModel(nil), watchers: make(map[string]*executor.Process), diff --git a/internal/devtui/model_update.go b/internal/devtui/model_update.go index 37d3b3c6..c279c6ad 100644 --- a/internal/devtui/model_update.go +++ b/internal/devtui/model_update.go @@ -321,7 +321,7 @@ func (m Model) startAfterSetupGuide() (tea.Model, tea.Cmd) { username = m.envConfig.AdminApi.Username password = m.envConfig.AdminApi.Password } - m.general = NewGeneralModel(m.executor.Type(), shopURL, username, password, m.projectRoot, m.executor) + m.general = NewGeneralModel(m.executor.Type(), shopURL, username, password, m.projectRoot, m.executor, m.config) m.configTab = NewConfigModel(m.config) return m, tea.Batch(m.dockerSpinner.Tick, m.startContainers()) diff --git a/internal/devtui/overlay_sales_channel_picker_test.go b/internal/devtui/overlay_sales_channel_picker_test.go index 93f09bf0..5a443425 100644 --- a/internal/devtui/overlay_sales_channel_picker_test.go +++ b/internal/devtui/overlay_sales_channel_picker_test.go @@ -56,7 +56,7 @@ func TestSalesChannelPicker_ConfirmEmitsWatcherOpts(t *testing.T) { func TestModel_SalesChannelPicker_FullRoutingFlow(t *testing.T) { m := Model{ phase: phaseDashboard, - general: NewGeneralModel("local", "http://localhost:8000", "", "", "/tmp/project", nil), + general: NewGeneralModel("local", "http://localhost:8000", "", "", "/tmp/project", nil, nil), watchers: make(map[string]*executor.Process), } diff --git a/internal/devtui/tab_general.go b/internal/devtui/tab_general.go index cb93dae2..a4463c35 100644 --- a/internal/devtui/tab_general.go +++ b/internal/devtui/tab_general.go @@ -11,7 +11,9 @@ import ( "github.com/shopware/shopware-cli/internal/executor" "github.com/shopware/shopware-cli/internal/extension" + "github.com/shopware/shopware-cli/internal/shop" "github.com/shopware/shopware-cli/internal/tui" + "github.com/shopware/shopware-cli/logging" ) type GeneralModel struct { @@ -23,6 +25,7 @@ type GeneralModel struct { services []DiscoveredService projectRoot string executor executor.Executor + shopCfg *shop.Config loading bool err error width int @@ -51,6 +54,7 @@ type watcherStartedMsg struct { } type watcherStoppedMsg struct { name string + err error } type knownService struct { @@ -72,7 +76,7 @@ var ignoredServices = map[string]bool{ "database": true, } -func NewGeneralModel(envType, shopURL, username, password, projectRoot string, exec executor.Executor) GeneralModel { +func NewGeneralModel(envType, shopURL, username, password, projectRoot string, exec executor.Executor, shopCfg *shop.Config) GeneralModel { adminURL := shopURL if adminURL != "" && !strings.HasSuffix(adminURL, "/") { adminURL += "/" @@ -87,6 +91,7 @@ func NewGeneralModel(envType, shopURL, username, password, projectRoot string, e password: password, projectRoot: projectRoot, executor: exec, + shopCfg: shopCfg, loading: true, } } @@ -161,11 +166,17 @@ func (m GeneralModel) View(width, height int) string { func (m *GeneralModel) startAdminWatch() tea.Cmd { e := m.executor projectRoot := m.projectRoot + shopCfg := m.shopCfg return func() tea.Msg { - watchProcess, err := extension.PrepareAdminWatcher(context.Background(), projectRoot, e) + ctx := logging.DisableLogger(context.Background()) + if err := extension.WriteProjectPluginJson(ctx, projectRoot, shopCfg, e); err != nil { + return watcherStoppedMsg{name: watcherAdmin, err: fmt.Errorf("preparing plugins.json: %w", err)} + } + + watchProcess, err := extension.PrepareAdminWatcher(ctx, projectRoot, e) if err != nil { - return watcherStoppedMsg{name: watcherAdmin} + return watcherStoppedMsg{name: watcherAdmin, err: fmt.Errorf("starting admin watcher: %w", err)} } return watcherStartedMsg{name: watcherAdmin, process: watchProcess} @@ -175,11 +186,17 @@ func (m *GeneralModel) startAdminWatch() tea.Cmd { func (m *GeneralModel) startStorefrontWatch(opts extension.StorefrontWatcherOptions) tea.Cmd { e := m.executor projectRoot := m.projectRoot + shopCfg := m.shopCfg return func() tea.Msg { - watchProcess, err := extension.PrepareStorefrontWatcher(context.Background(), projectRoot, e, opts) + ctx := logging.DisableLogger(context.Background()) + if err := extension.WriteProjectPluginJson(ctx, projectRoot, shopCfg, e); err != nil { + return watcherStoppedMsg{name: watcherStorefront, err: fmt.Errorf("preparing plugins.json: %w", err)} + } + + watchProcess, err := extension.PrepareStorefrontWatcher(ctx, projectRoot, e, opts) if err != nil { - return watcherStoppedMsg{name: watcherStorefront} + return watcherStoppedMsg{name: watcherStorefront, err: fmt.Errorf("starting storefront watcher: %w", err)} } return watcherStartedMsg{name: watcherStorefront, process: watchProcess} diff --git a/internal/devtui/tab_general_test.go b/internal/devtui/tab_general_test.go index 5d2bb6d7..6712b103 100644 --- a/internal/devtui/tab_general_test.go +++ b/internal/devtui/tab_general_test.go @@ -7,7 +7,7 @@ import ( ) func TestNewGeneralModel(t *testing.T) { - m := NewGeneralModel("docker", "http://localhost:8000", "admin", "shopware", "/tmp/project", nil) + m := NewGeneralModel("docker", "http://localhost:8000", "admin", "shopware", "/tmp/project", nil, nil) assert.Equal(t, "docker", m.envType) assert.Equal(t, "http://localhost:8000", m.shopURL) @@ -19,19 +19,19 @@ func TestNewGeneralModel(t *testing.T) { } func TestNewGeneralModel_AdminURLTrailingSlash(t *testing.T) { - m := NewGeneralModel("local", "http://localhost:8000/", "", "", "/tmp/project", nil) + m := NewGeneralModel("local", "http://localhost:8000/", "", "", "/tmp/project", nil, nil) assert.Equal(t, "http://localhost:8000/admin", m.adminURL) } func TestNewGeneralModel_EmptyURL(t *testing.T) { - m := NewGeneralModel("local", "", "", "", "/tmp/project", nil) + m := NewGeneralModel("local", "", "", "", "/tmp/project", nil, nil) assert.Equal(t, "admin", m.adminURL) } func TestServicesLoadedMsg(t *testing.T) { - m := NewGeneralModel("docker", "http://localhost:8000", "", "", "/tmp/project", nil) + m := NewGeneralModel("docker", "http://localhost:8000", "", "", "/tmp/project", nil, nil) services := []DiscoveredService{ {Name: "Adminer", URL: "http://127.0.0.1:9080", Username: "root", Password: "root"}, @@ -49,7 +49,7 @@ func TestServicesLoadedMsg(t *testing.T) { } func TestServicesLoadedMsg_WithError(t *testing.T) { - m := NewGeneralModel("docker", "http://localhost:8000", "", "", "/tmp/project", nil) + m := NewGeneralModel("docker", "http://localhost:8000", "", "", "/tmp/project", nil, nil) updated, _ := m.Update(servicesLoadedMsg{err: assert.AnError}) assert.False(t, updated.loading) @@ -78,7 +78,7 @@ func TestKnownServices(t *testing.T) { } func TestViewShowsCredentials(t *testing.T) { - m := NewGeneralModel("docker", "http://localhost:8000", "", "", "/tmp/project", nil) + m := NewGeneralModel("docker", "http://localhost:8000", "", "", "/tmp/project", nil, nil) m.loading = false m.services = []DiscoveredService{ {Name: "Adminer", URL: "http://127.0.0.1:9080", Username: "root", Password: "root"}, diff --git a/internal/devtui/tab_logs.go b/internal/devtui/tab_logs.go index b5ba6be0..d2c29547 100644 --- a/internal/devtui/tab_logs.go +++ b/internal/devtui/tab_logs.go @@ -250,6 +250,14 @@ func (m *LogsModel) StopStreaming() { m.stopStreaming() } +func (m *LogsModel) AppendErrorLine(msg string) { + m.lines = append(m.lines, errorStyle.Render(msg)) + m.viewport.SetContent(strings.Join(m.lines, "\n")) + if m.follow { + m.viewport.GotoBottom() + } +} + func (m *LogsModel) ActiveProcessSourceName() string { if m.active >= 0 && m.active < len(m.sources) { src := m.sources[m.active] diff --git a/internal/extension/project.go b/internal/extension/project.go index a093d753..2aaa5969 100644 --- a/internal/extension/project.go +++ b/internal/extension/project.go @@ -1,6 +1,7 @@ package extension import ( + "bytes" "context" "encoding/json" "fmt" @@ -185,11 +186,15 @@ type ConsoleCommandFunc func(ctx context.Context, args ...string) *exec.Cmd func DumpAndLoadAssetSourcesOfProject(ctx context.Context, project string, shopCfg *shop.Config, consoleCommand ConsoleCommandFunc) ([]asset.Source, error) { dumpExec := consoleCommand(ctx, "bundle:dump") dumpExec.Dir = project - dumpExec.Stdin = os.Stdin - dumpExec.Stdout = os.Stdout - dumpExec.Stderr = os.Stderr + // Capture output: bundle:dump's "Dumped plugin configuration." line corrupts the devtui render if inherited. + var dumpOutput bytes.Buffer + dumpExec.Stdout = &dumpOutput + dumpExec.Stderr = &dumpOutput if err := dumpExec.Run(); err != nil { + if out := strings.TrimSpace(dumpOutput.String()); out != "" { + return nil, fmt.Errorf("could not bundle features: %w: %s", err, out) + } return nil, fmt.Errorf("could not bundle features: %w", err) } diff --git a/internal/extension/project_plugin_json.go b/internal/extension/project_plugin_json.go new file mode 100644 index 00000000..798896c7 --- /dev/null +++ b/internal/extension/project_plugin_json.go @@ -0,0 +1,74 @@ +package extension + +import ( + "context" + "encoding/json" + "os" + "os/exec" + "path" + "slices" + + "github.com/shopware/shopware-cli/internal/asset" + "github.com/shopware/shopware-cli/internal/executor" + "github.com/shopware/shopware-cli/internal/shop" +) + +const storefrontBundleName = "Storefront" + +func LoadProjectAssetSources(ctx context.Context, projectRoot string, shopCfg *shop.Config, cmdExecutor executor.Executor) ([]asset.Source, error) { + return DumpAndLoadAssetSourcesOfProject(executor.AllowBinCI(ctx), projectRoot, shopCfg, func(ctx context.Context, args ...string) *exec.Cmd { + return cmdExecutor.ConsoleCommand(ctx, args...).Cmd + }) +} + +func ExcludeExtensionsFromSources(sources []asset.Source, excluded []string) []asset.Source { + if len(excluded) == 0 { + return sources + } + return slices.DeleteFunc(sources, func(s asset.Source) bool { + // Storefront must stay or the watchers break. + if s.Name == storefrontBundleName { + return false + } + return slices.Contains(excluded, s.Name) + }) +} + +func WriteProjectPluginJson(ctx context.Context, projectRoot string, shopCfg *shop.Config, cmdExecutor executor.Executor) error { + sources, err := LoadProjectAssetSources(ctx, projectRoot, shopCfg, cmdExecutor) + if err != nil { + return err + } + + sources = ExcludeExtensionsFromSources(sources, shopCfg.Build.ExcludeExtensions) + + return WritePluginJsonForSources(ctx, projectRoot, sources, cmdExecutor) +} + +func WritePluginJsonForSources(ctx context.Context, projectRoot string, sources []asset.Source, cmdExecutor executor.Executor) error { + assetConfig := AssetBuildConfig{ + ShopwareRoot: projectRoot, + Executor: cmdExecutor, + } + + cfgs := BuildAssetConfigFromExtensions(ctx, sources, assetConfig) + + if _, err := InstallNodeModulesOfConfigs(ctx, cfgs, assetConfig); err != nil { + return err + } + + // Normalize paths for the execution environment (e.g. Docker container). + for _, cfg := range cfgs { + cfg.BasePath = cmdExecutor.NormalizePath(cfg.BasePath) + for i, v := range cfg.Views { + cfg.Views[i] = cmdExecutor.NormalizePath(v) + } + } + + pluginJson, err := json.MarshalIndent(cfgs, "", " ") + if err != nil { + return err + } + + return os.WriteFile(path.Join(projectRoot, "var", "plugins.json"), pluginJson, os.ModePerm) +} From 6ae007b76a97cce892fee7c6aec83d8becbf612d Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Tue, 19 May 2026 11:28:43 +0200 Subject: [PATCH 2/2] fix: keep custom-static and only/skip-extensions composable The refactored switch made --only-custom-static-extensions mutually exclusive with --only-extensions / --skip-extensions, silently dropping the explicit filter. Restore the original semantics: custom-static is its own independent pass that runs first, then the only/skip/default exclusion runs on the narrowed list. The default exclude-list still skips when --only-custom-static is set (matching the original). --- cmd/project/platform.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/project/platform.go b/cmd/project/platform.go index c6f5352a..eaf72474 100644 --- a/cmd/project/platform.go +++ b/cmd/project/platform.go @@ -92,8 +92,7 @@ func filterAndGetSources(cmd *cobra.Command, projectRoot string, shopCfg *shop.C logger := logging.FromContext(cmd.Context()) - switch { - case onlyCustomStatic: + if onlyCustomStatic { logger.Infof("Only including extensions from custom/static-plugins directory") logger.Debugf("Found %d total extensions before filtering", len(sources)) for _, s := range sources { @@ -132,7 +131,9 @@ func filterAndGetSources(cmd *cobra.Command, projectRoot string, shopCfg *shop.C for _, s := range sources { logger.Debugf("Included extension: %s, Path: %s", s.Name, s.Path) } + } + switch { case onlyExtensions != "": logger.Infof("Only including extensions: %s", onlyExtensions) allowed := strings.Split(onlyExtensions, ",") @@ -148,7 +149,7 @@ func filterAndGetSources(cmd *cobra.Command, projectRoot string, shopCfg *shop.C logger.Infof("Excluding extensions: %s", skipExtensions) sources = extension.ExcludeExtensionsFromSources(sources, strings.Split(skipExtensions, ",")) - default: + case !onlyCustomStatic: logger.Infof("Excluding extensions based on project config: %s", strings.Join(shopCfg.Build.ExcludeExtensions, ", ")) sources = extension.ExcludeExtensionsFromSources(sources, shopCfg.Build.ExcludeExtensions) }