diff --git a/cmd/extension/extension_build.go b/cmd/extension/extension_build.go index 7bc64cb1..d2e71512 100644 --- a/cmd/extension/extension_build.go +++ b/cmd/extension/extension_build.go @@ -17,9 +17,7 @@ var extensionAssetBundleCmd = &cobra.Command{ Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { assetCfg := extension.AssetBuildConfig{ - EnableESBuildForAdmin: false, - EnableESBuildForStorefront: false, - ShopwareRoot: os.Getenv("SHOPWARE_PROJECT_ROOT"), + ShopwareRoot: os.Getenv("SHOPWARE_PROJECT_ROOT"), } validatedExtensions := make([]extension.Extension, 0) @@ -37,13 +35,6 @@ var extensionAssetBundleCmd = &cobra.Command{ validatedExtensions = append(validatedExtensions, ext) } - if len(args) == 1 { - extCfg := validatedExtensions[0].GetExtensionConfig() - - assetCfg.EnableESBuildForAdmin = extCfg.Build.Zip.Assets.EnableESBuildForAdmin - assetCfg.EnableESBuildForStorefront = extCfg.Build.Zip.Assets.EnableESBuildForStorefront - } - constraint, err := validatedExtensions[0].GetShopwareVersionConstraint() if err != nil { return fmt.Errorf("cannot get shopware version constraint: %w", err) diff --git a/cmd/extension/extension_zip.go b/cmd/extension/extension_zip.go index 40d89ced..9b487d49 100644 --- a/cmd/extension/extension_zip.go +++ b/cmd/extension/extension_zip.go @@ -137,11 +137,9 @@ var extensionZipCmd = &cobra.Command{ } assetBuildConfig := extension.AssetBuildConfig{ - EnableESBuildForAdmin: extCfg.Build.Zip.Assets.EnableESBuildForAdmin, - EnableESBuildForStorefront: extCfg.Build.Zip.Assets.EnableESBuildForStorefront, - CleanupNodeModules: true, - ShopwareRoot: os.Getenv("SHOPWARE_PROJECT_ROOT"), - ShopwareVersion: shopwareConstraint, + CleanupNodeModules: true, + ShopwareRoot: os.Getenv("SHOPWARE_PROJECT_ROOT"), + ShopwareVersion: shopwareConstraint, } if err := extension.BuildAssetsForExtensions(cmd.Context(), extension.ConvertExtensionsToSources(cmd.Context(), []extension.Extension{tempExt}), assetBuildConfig); err != nil { diff --git a/cmd/project/ci.go b/cmd/project/ci.go index de5221e8..3c801a75 100644 --- a/cmd/project/ci.go +++ b/cmd/project/ci.go @@ -74,8 +74,6 @@ var projectCI = &cobra.Command{ } assetCfg := extension.AssetBuildConfig{ - EnableESBuildForAdmin: false, - EnableESBuildForStorefront: false, CleanupNodeModules: true, ShopwareRoot: args[0], ShopwareVersion: constraint, diff --git a/extension/asset.go b/extension/asset.go index b962eecb..272cb940 100644 --- a/extension/asset.go +++ b/extension/asset.go @@ -20,8 +20,10 @@ func ConvertExtensionsToSources(ctx context.Context, extensions []Extension) []a } sources = append(sources, asset.Source{ - Name: name, - Path: ext.GetRootDir(), + Name: name, + Path: ext.GetRootDir(), + AdminEsbuildCompatible: ext.GetExtensionConfig().Build.Zip.Assets.EnableESBuildForAdmin, + StorefrontEsbuildCompatible: ext.GetExtensionConfig().Build.Zip.Assets.EnableESBuildForStorefront, }) extConfig := ext.GetExtensionConfig() @@ -35,8 +37,10 @@ func ConvertExtensionsToSources(ctx context.Context, extensions []Extension) []a } sources = append(sources, asset.Source{ - Name: bundleName, - Path: path.Join(ext.GetRootDir(), bundle.Path), + Name: bundleName, + Path: path.Join(ext.GetRootDir(), bundle.Path), + AdminEsbuildCompatible: ext.GetExtensionConfig().Build.Zip.Assets.EnableESBuildForAdmin, + StorefrontEsbuildCompatible: ext.GetExtensionConfig().Build.Zip.Assets.EnableESBuildForStorefront, }) } } diff --git a/extension/asset_platform.go b/extension/asset_platform.go index 1ab780bb..bbf89f2d 100644 --- a/extension/asset_platform.go +++ b/extension/asset_platform.go @@ -27,8 +27,6 @@ const ( ) type AssetBuildConfig struct { - EnableESBuildForAdmin bool - EnableESBuildForStorefront bool CleanupNodeModules bool DisableAdminBuild bool DisableStorefrontBuild bool @@ -41,7 +39,7 @@ type AssetBuildConfig struct { func BuildAssetsForExtensions(ctx context.Context, sources []asset.Source, assetConfig AssetBuildConfig) error { // nolint:gocyclo cfgs := buildAssetConfigFromExtensions(ctx, sources, assetConfig) - if len(cfgs) == 1 { + if len(cfgs) == 0 { return nil } @@ -50,78 +48,44 @@ func BuildAssetsForExtensions(ctx context.Context, sources []asset.Source, asset return nil } - buildWithoutShopwareSource := assetConfig.EnableESBuildForStorefront && assetConfig.EnableESBuildForAdmin + requiresShopwareSources := cfgs.RequiresShopwareRepository() shopwareRoot := assetConfig.ShopwareRoot var err error - if shopwareRoot == "" && !buildWithoutShopwareSource { + if shopwareRoot == "" && requiresShopwareSources { shopwareRoot, err = setupShopwareInTemp(ctx, assetConfig.ShopwareVersion) if err != nil { return err } - defer deletePath(ctx, shopwareRoot) + defer deletePaths(ctx, shopwareRoot) } - if !buildWithoutShopwareSource { - if err := prepareShopwareForAsset(shopwareRoot, cfgs); err != nil { - return err - } + paths, err := installNodeModulesOfConfigs(cfgs) + if err != nil { + return err } - // Install shared node_modules between admin and storefront - for _, entry := range cfgs { - // Install also shared node_modules - if _, err := os.Stat(filepath.Join(entry.BasePath, "Resources", "app", "package.json")); err == nil { - npmPath := filepath.Join(entry.BasePath, "Resources", "app") - if err := installDependencies(npmPath); err != nil { - return err - } + defer deletePaths(ctx, paths...) - if assetConfig.CleanupNodeModules { - defer deletePath(ctx, path.Join(npmPath, "node_modules")) - } - } + if !assetConfig.DisableAdminBuild && cfgs.RequiresAdminBuild() { + // Build all extensions compatible with esbuild first + for name, entry := range cfgs.FilterByAdmin(true) { + options := esbuild.NewAssetCompileOptionsAdmin(name, entry.BasePath) - if _, err := os.Stat(filepath.Join(entry.BasePath, "Resources", "app", "administration", "package.json")); err == nil { - npmPath := filepath.Join(entry.BasePath, "Resources", "app", "administration") - if err := installDependencies(npmPath); err != nil { + if _, err := esbuild.CompileExtensionAsset(ctx, options); err != nil { return err } - - if assetConfig.CleanupNodeModules { - defer deletePath(ctx, path.Join(npmPath, "node_modules")) - } } - if _, err := os.Stat(filepath.Join(entry.BasePath, "Resources", "app", "storefront", "package.json")); err == nil { - npmPath := filepath.Join(entry.BasePath, "Resources", "app", "storefront") - err := installDependencies(npmPath) - if err != nil { + nonCompatibleExtensions := cfgs.FilterByAdmin(false) + + if len(nonCompatibleExtensions) != 0 { + if err := prepareShopwareForAsset(shopwareRoot, nonCompatibleExtensions); err != nil { return err } - if assetConfig.CleanupNodeModules { - defer deletePath(ctx, path.Join(npmPath, "node_modules")) - } - } - } - - if !assetConfig.DisableAdminBuild && cfgs.RequiresAdminBuild() { - if assetConfig.EnableESBuildForAdmin { - for _, source := range sources { - if !cfgs.Has(source.Name) { - continue - } - - options := esbuild.NewAssetCompileOptionsAdmin(source.Name, source.Path) - - if _, err := esbuild.CompileExtensionAsset(ctx, options); err != nil { - return err - } - } - } else { administrationRoot := PlatformPath(shopwareRoot, "Administration", "Resources/app/administration") err := npmRunBuild( administrationRoot, @@ -130,8 +94,7 @@ func BuildAssetsForExtensions(ctx context.Context, sources []asset.Source, asset ) if assetConfig.CleanupNodeModules { - defer deletePath(ctx, path.Join(administrationRoot, "node_modules")) - defer deletePath(ctx, path.Join(administrationRoot, "twigVuePlugin")) + defer deletePaths(ctx, path.Join(administrationRoot, "node_modules"), path.Join(administrationRoot, "twigVuePlugin")) } if err != nil { @@ -141,18 +104,48 @@ func BuildAssetsForExtensions(ctx context.Context, sources []asset.Source, asset } if !assetConfig.DisableStorefrontBuild && cfgs.RequiresStorefrontBuild() { - if assetConfig.EnableESBuildForStorefront { - for _, source := range sources { - if !cfgs.Has(source.Name) { - continue - } + // Build all extensions compatible with esbuild first + for name, entry := range cfgs.FilterByStorefront(true) { + options := esbuild.NewAssetCompileOptionsStorefront(name, entry.BasePath) - options := esbuild.NewAssetCompileOptionsStorefront(source.Name, source.Path) - if _, err := esbuild.CompileExtensionAsset(ctx, options); err != nil { - return err - } + if _, err := esbuild.CompileExtensionAsset(ctx, options); err != nil { + return err } - } else { + } + + nonCompatibleExtensions := cfgs.FilterByStorefront(false) + + if len(nonCompatibleExtensions) != 0 { + // add the storefront itself as plugin into json + var basePath string + if shopwareRoot == "" { + basePath = "src/Storefront/" + } else { + basePath = strings.TrimLeft( + strings.Replace(PlatformPath(shopwareRoot, "Storefront", ""), shopwareRoot, "", 1), + "/", + ) + "/" + } + + entryPath := "Resources/app/storefront/src/main.js" + nonCompatibleExtensions["Storefront"] = ExtensionAssetConfigEntry{ + BasePath: basePath, + Views: []string{"Resources/views"}, + TechnicalName: "storefront", + Storefront: ExtensionAssetConfigStorefront{ + Path: "Resources/app/storefront/src", + EntryFilePath: &entryPath, + StyleFiles: []string{}, + }, + Administration: ExtensionAssetConfigAdmin{ + Path: "Resources/app/administration/src", + }, + } + + if err := prepareShopwareForAsset(shopwareRoot, nonCompatibleExtensions); err != nil { + return err + } + storefrontRoot := PlatformPath(shopwareRoot, "Storefront", "Resources/app/storefront") envList := []string{ @@ -180,7 +173,7 @@ func BuildAssetsForExtensions(ctx context.Context, sources []asset.Source, asset ) if assetConfig.CleanupNodeModules { - defer deletePath(ctx, path.Join(storefrontRoot, "node_modules")) + defer deletePaths(ctx, path.Join(storefrontRoot, "node_modules")) } if err != nil { @@ -192,10 +185,50 @@ func BuildAssetsForExtensions(ctx context.Context, sources []asset.Source, asset return nil } -func deletePath(ctx context.Context, path string) { - if err := os.RemoveAll(path); err != nil { - logging.FromContext(ctx).Errorf("Failed to remove path %s: %s", path, err.Error()) - return +func installNodeModulesOfConfigs(cfgs ExtensionAssetConfig) ([]string, error) { + paths := make([]string, 0) + + // Install shared node_modules between admin and storefront + for _, entry := range cfgs { + // Install also shared node_modules + if _, err := os.Stat(filepath.Join(entry.BasePath, "Resources", "app", "package.json")); err == nil { + npmPath := filepath.Join(entry.BasePath, "Resources", "app") + if err := installDependencies(npmPath); err != nil { + return nil, err + } + + paths = append(paths, path.Join(npmPath, "node_modules")) + } + + if _, err := os.Stat(filepath.Join(entry.BasePath, "Resources", "app", "administration", "package.json")); err == nil { + npmPath := filepath.Join(entry.BasePath, "Resources", "app", "administration") + if err := installDependencies(npmPath); err != nil { + return nil, err + } + + paths = append(paths, path.Join(npmPath, "node_modules")) + } + + if _, err := os.Stat(filepath.Join(entry.BasePath, "Resources", "app", "storefront", "package.json")); err == nil { + npmPath := filepath.Join(entry.BasePath, "Resources", "app", "storefront") + err := installDependencies(npmPath) + if err != nil { + return nil, err + } + + paths = append(paths, path.Join(npmPath, "node_modules")) + } + } + + return paths, nil +} + +func deletePaths(ctx context.Context, nodeModulesPaths ...string) { + for _, nodeModulesPath := range nodeModulesPaths { + if err := os.RemoveAll(nodeModulesPath); err != nil { + logging.FromContext(ctx).Errorf("Failed to remove path %s: %s", nodeModulesPath, err.Error()) + return + } } } @@ -292,6 +325,8 @@ func buildAssetConfigFromExtensions(ctx context.Context, sources []asset.Source, } sourceConfig := createConfigFromPath(source.Name, source.Path) + sourceConfig.EnableESBuildForAdmin = source.AdminEsbuildCompatible + sourceConfig.EnableESBuildForStorefront = source.StorefrontEsbuildCompatible if assetCfg.SkipExtensionsWithBuildFiles { expectedAdminCompiledFile := path.Join(source.Path, "Resources", "public", "administration", "js", esbuild.ToKebabCase(source.Name)+".js") @@ -308,31 +343,6 @@ func buildAssetConfigFromExtensions(ctx context.Context, sources []asset.Source, list[source.Name] = sourceConfig } - var basePath string - if assetCfg.ShopwareRoot == "" { - basePath = "src/Storefront/" - } else { - basePath = strings.TrimLeft( - strings.Replace(PlatformPath(assetCfg.ShopwareRoot, "Storefront", ""), assetCfg.ShopwareRoot, "", 1), - "/", - ) + "/" - } - - entryPath := "Resources/app/storefront/src/main.js" - list["Storefront"] = ExtensionAssetConfigEntry{ - BasePath: basePath, - Views: []string{"Resources/views"}, - TechnicalName: "storefront", - Storefront: ExtensionAssetConfigStorefront{ - Path: "Resources/app/storefront/src", - EntryFilePath: &entryPath, - StyleFiles: []string{}, - }, - Administration: ExtensionAssetConfigAdmin{ - Path: "Resources/app/administration/src", - }, - } - return list } @@ -419,6 +429,8 @@ func setupShopwareInTemp(ctx context.Context, shopwareVersionConstraint *version if shopware66Constraint.Check(version.Must(version.NewVersion(minVersion))) { cloneBranch = "trunk" + } else if version.MustConstraints(version.NewConstraint("~6.5.0")).Check(version.Must(version.NewVersion(minVersion))) { + cloneBranch = "6.5.x" } logging.FromContext(ctx).Infof("Cloning shopware with branch: %s into %s", cloneBranch, dir) @@ -443,6 +455,20 @@ func (c ExtensionAssetConfig) Has(name string) bool { return ok } +func (c ExtensionAssetConfig) RequiresShopwareRepository() bool { + for _, entry := range c { + if entry.Administration.EntryFilePath != nil && !entry.EnableESBuildForAdmin { + return true + } + + if entry.Storefront.EntryFilePath != nil && !entry.EnableESBuildForStorefront { + return true + } + } + + return false +} + func (c ExtensionAssetConfig) RequiresAdminBuild() bool { for _, entry := range c { if entry.Administration.EntryFilePath != nil { @@ -455,10 +481,6 @@ func (c ExtensionAssetConfig) RequiresAdminBuild() bool { func (c ExtensionAssetConfig) RequiresStorefrontBuild() bool { for _, entry := range c { - if entry.TechnicalName == "storefront" { - continue - } - if entry.Storefront.EntryFilePath != nil { return true } @@ -467,12 +489,38 @@ func (c ExtensionAssetConfig) RequiresStorefrontBuild() bool { return false } +func (c ExtensionAssetConfig) FilterByAdmin(esbuildEnabled bool) ExtensionAssetConfig { + filtered := make(ExtensionAssetConfig) + + for name, entry := range c { + if entry.Administration.EntryFilePath != nil && entry.EnableESBuildForAdmin == esbuildEnabled { + filtered[name] = entry + } + } + + return filtered +} + +func (c ExtensionAssetConfig) FilterByStorefront(esbuildEnabled bool) ExtensionAssetConfig { + filtered := make(ExtensionAssetConfig) + + for name, entry := range c { + if entry.Storefront.EntryFilePath != nil && entry.EnableESBuildForStorefront == esbuildEnabled { + filtered[name] = entry + } + } + + return filtered +} + type ExtensionAssetConfigEntry struct { - BasePath string `json:"basePath"` - Views []string `json:"views"` - TechnicalName string `json:"technicalName"` - Administration ExtensionAssetConfigAdmin `json:"administration"` - Storefront ExtensionAssetConfigStorefront `json:"storefront"` + BasePath string `json:"basePath"` + Views []string `json:"views"` + TechnicalName string `json:"technicalName"` + Administration ExtensionAssetConfigAdmin `json:"administration"` + Storefront ExtensionAssetConfigStorefront `json:"storefront"` + EnableESBuildForAdmin bool + EnableESBuildForStorefront bool } type ExtensionAssetConfigAdmin struct { diff --git a/extension/asset_platform_test.go b/extension/asset_platform_test.go index 786dff40..e9c07aeb 100644 --- a/extension/asset_platform_test.go +++ b/extension/asset_platform_test.go @@ -90,5 +90,5 @@ func TestGenerateConfigDoesNotAddExtensionWithoutName(t *testing.T) { config := buildAssetConfigFromExtensions(getTestContext(), []asset.Source{{Name: "", Path: dir}}, AssetBuildConfig{}) - assert.Len(t, config, 1) + assert.Len(t, config, 0) } diff --git a/extension/asset_test.go b/extension/asset_test.go index e734a13f..19ac53f7 100644 --- a/extension/asset_test.go +++ b/extension/asset_test.go @@ -9,7 +9,8 @@ import ( func TestConvertPlugin(t *testing.T) { plugin := PlatformPlugin{ - path: t.TempDir(), + path: t.TempDir(), + config: &Config{}, composer: platformComposerJson{ Extra: platformComposerJsonExtra{ ShopwarePluginClass: "FroshTools\\FroshTools", @@ -28,7 +29,8 @@ func TestConvertPlugin(t *testing.T) { func TestConvertApp(t *testing.T) { app := App{ - path: t.TempDir(), + path: t.TempDir(), + config: &Config{}, manifest: appManifest{ Meta: appManifestMeta{ Name: "TestApp", diff --git a/extension/project.go b/extension/project.go index 49d1aeb4..b800ace8 100644 --- a/extension/project.go +++ b/extension/project.go @@ -66,9 +66,18 @@ func FindAssetSourcesOfProject(ctx context.Context, project string) []asset.Sour logging.FromContext(ctx).Infof("Found bundle in project: %s (path: %s)", name, bundlePath) + bundleConfig, err := readExtensionConfig(bundlePath) + + if err != nil { + logging.FromContext(ctx).Errorf("Cannot read bundle config: %s", err.Error()) + continue + } + sources = append(sources, asset.Source{ - Name: name, - Path: path.Join(project, bundlePath), + Name: name, + Path: path.Join(project, bundlePath), + AdminEsbuildCompatible: bundleConfig.Build.Zip.Assets.EnableESBuildForAdmin, + StorefrontEsbuildCompatible: bundleConfig.Build.Zip.Assets.EnableESBuildForStorefront, }) } diff --git a/internal/asset/source.go b/internal/asset/source.go index 8f79f626..1568dc3f 100644 --- a/internal/asset/source.go +++ b/internal/asset/source.go @@ -1,6 +1,8 @@ package asset type Source struct { - Name string - Path string + Name string + Path string + AdminEsbuildCompatible bool + StorefrontEsbuildCompatible bool }