浏览代码

feat: commandline for bundle packager

Yeuoly 9 月之前
父节点
当前提交
ef54a244d0

+ 19 - 9
cmd/commandline/bundle.go

@@ -24,6 +24,8 @@ var (
 		Short: "List all dependencies",
 		Long:  "List all dependencies",
 		Run: func(c *cobra.Command, args []string) {
+			bundlePath := c.Flag("bundle_path").Value.String()
+			bundle.ListDependencies(bundlePath)
 		},
 	}
 
@@ -81,6 +83,8 @@ var (
 		Short: "Regenerate the bundle",
 		Long:  "Regenerate the bundle",
 		Run: func(c *cobra.Command, args []string) {
+			bundlePath := c.Flag("bundle_path").Value.String()
+			bundle.RegenerateBundle(bundlePath)
 		},
 	}
 
@@ -105,14 +109,9 @@ var (
 		Short: "Bump the version of the bundle",
 		Long:  "Bump the version of the bundle",
 		Run: func(c *cobra.Command, args []string) {
-		},
-	}
-
-	bundleListDependenciesCommand = &cobra.Command{
-		Use:   "list",
-		Short: "List all dependencies",
-		Long:  "List all dependencies",
-		Run: func(c *cobra.Command, args []string) {
+			bundlePath := c.Flag("bundle_path").Value.String()
+			targetVersion := c.Flag("target_version").Value.String()
+			bundle.BumpVersion(bundlePath, targetVersion)
 		},
 	}
 
@@ -121,6 +120,9 @@ var (
 		Short: "Package the bundle",
 		Long:  "Package the bundle",
 		Run: func(c *cobra.Command, args []string) {
+			bundlePath := c.Flag("bundle_path").Value.String()
+			outputPath := c.Flag("output_path").Value.String()
+			bundle.PackageBundle(bundlePath, outputPath)
 		},
 	}
 )
@@ -134,7 +136,6 @@ func init() {
 	bundleCommand.AddCommand(bundleRemoveDependencyCommand)
 	bundleCommand.AddCommand(bundleRegenerateCommand)
 	bundleCommand.AddCommand(bundleBumpVersionCommand)
-	bundleCommand.AddCommand(bundleListDependenciesCommand)
 	bundleCommand.AddCommand(bundlePackageCommand)
 	bundleCommand.AddCommand(bundleAnalyzeCommand)
 
@@ -149,4 +150,13 @@ func init() {
 
 	bundleAppendPackageDependencyCommand.Flags().StringP("package_path", "p", "", "path to the package")
 	bundleAppendPackageDependencyCommand.MarkFlagRequired("package_path")
+
+	bundleRemoveDependencyCommand.Flags().StringP("index", "i", "", "index of the dependency")
+	bundleRemoveDependencyCommand.MarkFlagRequired("index")
+
+	bundleBumpVersionCommand.Flags().StringP("target_version", "t", "", "target version")
+	bundleBumpVersionCommand.MarkFlagRequired("target_version")
+
+	bundlePackageCommand.Flags().StringP("output_path", "o", "", "output path")
+	bundlePackageCommand.MarkFlagRequired("output_path")
 }

+ 0 - 19
cmd/commandline/bundle/add_dep.go

@@ -1,19 +0,0 @@
-package bundle
-
-import "github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
-
-func AddGithubDependency(bundlePath string, pattern bundle_entities.GithubRepoPattern) {
-
-}
-
-func AddMarketplaceDependency(bundlePath string, pattern bundle_entities.MarketplacePattern) {
-
-}
-
-func AddPackageDependency(bundlePath string, path string) {
-
-}
-
-func RemoveDependency(bundlePath string, index int) {
-
-}

+ 26 - 0
cmd/commandline/bundle/bump_version.go

@@ -0,0 +1,26 @@
+package bundle
+
+import (
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/manifest_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/log"
+)
+
+func BumpVersion(bundlePath string, targetVersion string) {
+	packager, err := loadBundlePackager(bundlePath)
+	if err != nil {
+		log.Error("Failed to load bundle packager: %v", err)
+		return
+	}
+
+	targetVersionObject, err := manifest_entities.NewVersion(targetVersion)
+	if err != nil {
+		log.Error("Failed to parse target version: %v", err)
+		return
+	}
+
+	packager.BumpVersion(targetVersionObject)
+	if err := packager.Save(); err != nil {
+		log.Error("Failed to save bundle packager: %v", err)
+		return
+	}
+}

+ 177 - 0
cmd/commandline/bundle/dep.go

@@ -0,0 +1,177 @@
+package bundle
+
+import (
+	"os"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/core/bundle_packager"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/log"
+)
+
+func loadBundlePackager(bundlePath string) (bundle_packager.BundlePackager, error) {
+	// state file, check if it's a file or a directory
+	stateFile, err := os.Stat(bundlePath)
+	if err != nil {
+		return nil, err
+	}
+
+	if stateFile.IsDir() {
+		return bundle_packager.NewLocalBundlePackager(bundlePath)
+	}
+
+	return bundle_packager.NewZipBundlePackager(bundlePath)
+}
+
+func AddGithubDependency(bundlePath string, pattern bundle_entities.GithubRepoPattern) {
+	packager, err := loadBundlePackager(bundlePath)
+	if err != nil {
+		log.Error("Failed to load bundle packager: %v", err)
+		return
+	}
+
+	packager.AppendGithubDependency(pattern)
+	if err := packager.Save(); err != nil {
+		log.Error("Failed to save bundle packager: %v", err)
+		return
+	}
+
+	log.Info("Successfully added github dependency")
+}
+
+func AddMarketplaceDependency(bundlePath string, pattern bundle_entities.MarketplacePattern) {
+	packager, err := loadBundlePackager(bundlePath)
+	if err != nil {
+		log.Error("Failed to load bundle packager: %v", err)
+		return
+	}
+
+	packager.AppendMarketplaceDependency(pattern)
+	if err := packager.Save(); err != nil {
+		log.Error("Failed to save bundle packager: %v", err)
+		return
+	}
+
+	log.Info("Successfully added marketplace dependency")
+}
+
+func AddPackageDependency(bundlePath string, path string) {
+	packager, err := loadBundlePackager(bundlePath)
+	if err != nil {
+		log.Error("Failed to load bundle packager: %v", err)
+		return
+	}
+
+	if err := packager.AppendPackageDependency(path); err != nil {
+		log.Error("Failed to append package dependency: %v", err)
+		return
+	}
+
+	log.Info("Successfully added package dependency")
+}
+
+func RegenerateBundle(bundlePath string) {
+	bundle, err := generateNewBundle()
+	if err != nil {
+		log.Error("Failed to generate new bundle: %v", err)
+		return
+	}
+
+	packager, err := loadBundlePackager(bundlePath)
+	if err != nil {
+		log.Error("Failed to load bundle packager: %v", err)
+		return
+	}
+
+	packager.Regenerate(*bundle)
+	if err := packager.Save(); err != nil {
+		log.Error("Failed to save bundle packager: %v", err)
+		return
+	}
+
+	log.Info("Successfully regenerated bundle")
+}
+
+func RemoveDependency(bundlePath string, index int) {
+	packager, err := loadBundlePackager(bundlePath)
+	if err != nil {
+		log.Error("Failed to load bundle packager: %v", err)
+		return
+	}
+
+	if err := packager.Remove(index); err != nil {
+		log.Error("Failed to remove dependency: %v", err)
+		return
+	}
+
+	log.Info("Successfully removed dependency")
+}
+
+func PackageBundle(bundlePath string, outputPath string) {
+	packager, err := loadBundlePackager(bundlePath)
+	if err != nil {
+		log.Error("Failed to load bundle packager: %v", err)
+		return
+	}
+
+	zipFile, err := packager.Export()
+	if err != nil {
+		log.Error("Failed to export bundle: %v", err)
+		return
+	}
+
+	if err := os.WriteFile(outputPath, zipFile, 0644); err != nil {
+		log.Error("Failed to write zip file: %v", err)
+		return
+	}
+
+	log.Info("Successfully packaged bundle")
+}
+
+func ListDependencies(bundlePath string) {
+	packager, err := loadBundlePackager(bundlePath)
+	if err != nil {
+		log.Error("Failed to load bundle packager: %v", err)
+		return
+	}
+
+	dependencies, err := packager.ListDependencies()
+	if err != nil {
+		log.Error("Failed to list dependencies: %v", err)
+		return
+	}
+
+	for i, dependency := range dependencies {
+		log.Info("========== Dependency %d ==========", i)
+		if dependency.Type == bundle_entities.DEPENDENCY_TYPE_GITHUB {
+			githubDependency, ok := dependency.Value.(bundle_entities.GithubDependency)
+			if !ok {
+				log.Error("Failed to assert github pattern")
+				continue
+			}
+
+			log.Info("Dependency Type: Github, Pattern: %s", githubDependency.RepoPattern)
+			log.Info("Github Repo: %s", githubDependency.RepoPattern.Repo())
+			log.Info("Release: %s", githubDependency.RepoPattern.Release())
+			log.Info("Asset: %s", githubDependency.RepoPattern.Asset())
+		} else if dependency.Type == bundle_entities.DEPENDENCY_TYPE_MARKETPLACE {
+			marketplaceDependency, ok := dependency.Value.(bundle_entities.MarketplaceDependency)
+			if !ok {
+				log.Error("Failed to assert marketplace pattern")
+				continue
+			}
+
+			log.Info("Dependency Type: Marketplace, Pattern: %s", marketplaceDependency.MarketplacePattern)
+			log.Info("Organization: %s", marketplaceDependency.MarketplacePattern.Organization())
+			log.Info("Plugin: %s", marketplaceDependency.MarketplacePattern.Plugin())
+			log.Info("Version: %s", marketplaceDependency.MarketplacePattern.Version())
+		} else if dependency.Type == bundle_entities.DEPENDENCY_TYPE_PACKAGE {
+			packageDependency, ok := dependency.Value.(bundle_entities.PackageDependency)
+			if !ok {
+				log.Error("Failed to assert package dependency")
+				continue
+			}
+
+			log.Info("Dependency Type: Package, Path: %s", packageDependency.Path)
+		}
+	}
+}

+ 52 - 43
cmd/commandline/bundle/init.go

@@ -2,7 +2,7 @@ package bundle
 
 import (
 	_ "embed"
-	"fmt"
+	"errors"
 	"os"
 	"path"
 
@@ -18,11 +18,11 @@ import (
 //go:embed templates/icon.svg
 var BUNDLE_ICON []byte
 
-func InitBundle() {
+func generateNewBundle() (*bundle_entities.Bundle, error) {
 	m := newProfile()
 	p := tea.NewProgram(m)
 	if result, err := p.Run(); err != nil {
-		fmt.Println("Error running program:", err)
+		return nil, err
 	} else {
 		if _, ok := result.(profile); ok {
 			author := m.inputs[1].Value()
@@ -40,51 +40,60 @@ func InitBundle() {
 				Dependencies: []bundle_entities.Dependency{},
 			}
 
-			// create bundle directory
-			cwd, err := os.Getwd()
-			if err != nil {
-				log.Error("Error getting current directory: %v", err)
-				return
-			}
+			return bundle, nil
+		} else {
+			return nil, errors.New("invalid profile")
+		}
+	}
+}
 
-			bundleDir := path.Join(cwd, bundle.Name)
-			if err := os.MkdirAll(bundleDir, 0755); err != nil {
-				log.Error("Error creating bundle directory: %v", err)
-				return
-			}
+func InitBundle() {
+	bundle, err := generateNewBundle()
+	if err != nil {
+		log.Error("Failed to generate new bundle: %v", err)
+		return
+	}
 
-			success := false
-			defer func() {
-				if !success {
-					os.RemoveAll(bundleDir)
-				}
-			}()
-
-			// save
-			bundleYaml := parser.MarshalYamlBytes(bundle)
-			if err := os.WriteFile(path.Join(bundleDir, "manifest.yaml"), bundleYaml, 0644); err != nil {
-				log.Error("Error saving manifest.yaml: %v", err)
-				return
-			}
+	// create bundle directory
+	cwd, err := os.Getwd()
+	if err != nil {
+		log.Error("Error getting current directory: %v", err)
+		return
+	}
 
-			// create _assets directory
-			if err := os.MkdirAll(path.Join(bundleDir, "_assets"), 0755); err != nil {
-				log.Error("Error creating _assets directory: %v", err)
-				return
-			}
+	bundleDir := path.Join(cwd, bundle.Name)
+	if err := os.MkdirAll(bundleDir, 0755); err != nil {
+		log.Error("Error creating bundle directory: %v", err)
+		return
+	}
 
-			// create _assets/icon.svg
-			if err := os.WriteFile(path.Join(bundleDir, "_assets", "icon.svg"), BUNDLE_ICON, 0644); err != nil {
-				log.Error("Error saving icon.svg: %v", err)
-				return
-			}
+	success := false
+	defer func() {
+		if !success {
+			os.RemoveAll(bundleDir)
+		}
+	}()
 
-			success = true
+	// save
+	bundleYaml := parser.MarshalYamlBytes(bundle)
+	if err := os.WriteFile(path.Join(bundleDir, "manifest.yaml"), bundleYaml, 0644); err != nil {
+		log.Error("Error saving manifest.yaml: %v", err)
+		return
+	}
 
-			log.Info("Bundle created successfully: %s", bundleDir)
-		} else {
-			log.Error("Error running program: %v", err)
-			return
-		}
+	// create _assets directory
+	if err := os.MkdirAll(path.Join(bundleDir, "_assets"), 0755); err != nil {
+		log.Error("Error creating _assets directory: %v", err)
+		return
+	}
+
+	// create _assets/icon.svg
+	if err := os.WriteFile(path.Join(bundleDir, "_assets", "icon.svg"), BUNDLE_ICON, 0644); err != nil {
+		log.Error("Error saving icon.svg: %v", err)
+		return
 	}
+
+	success = true
+
+	log.Info("Bundle created successfully: %s", bundleDir)
 }

+ 1 - 0
cmd/commandline/bundle/regenerate.go

@@ -0,0 +1 @@
+package bundle

+ 7 - 3
internal/core/bundle_packager/bundle_packager.go

@@ -2,14 +2,15 @@ package bundle_packager
 
 import (
 	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/manifest_entities"
 )
 
 type BundlePackager interface {
 	// Export exports the bundle to a zip byte array
 	Export() ([]byte, error)
 
-	// Icon returns the icon of the bundle
-	Icon() ([]byte, error)
+	// Save saves the bundle to the original source
+	Save() error
 
 	// Manifest returns the manifest of the bundle
 	Manifest() (*bundle_entities.Bundle, error)
@@ -24,11 +25,14 @@ type BundlePackager interface {
 	AppendMarketplaceDependency(marketplacePattern bundle_entities.MarketplacePattern)
 
 	// Append Package Dependency appends a local package dependency to the bundle
-	AppendPackageDependency(packagePath string)
+	AppendPackageDependency(packagePath string) error
 
 	// ListDependencies lists all the dependencies of the bundle
 	ListDependencies() ([]bundle_entities.Dependency, error)
 
 	// Regenerate regenerates the bundle, replace the basic information of the bundle like name, labels, description, icon, etc.
 	Regenerate(bundle bundle_entities.Bundle) error
+
+	// BumpVersion bumps the version of the bundle
+	BumpVersion(targetVersion manifest_entities.Version)
 }

+ 148 - 0
internal/core/bundle_packager/generic.go

@@ -0,0 +1,148 @@
+package bundle_packager
+
+import (
+	"archive/zip"
+	"bytes"
+	"errors"
+	"os"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/core/plugin_packager/decoder"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/manifest_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
+)
+
+type GenericBundlePackager struct {
+	bundle *bundle_entities.Bundle
+	assets map[string]*bytes.Buffer
+}
+
+func NewGenericBundlePackager(bundle *bundle_entities.Bundle) *GenericBundlePackager {
+	return &GenericBundlePackager{bundle: bundle, assets: make(map[string]*bytes.Buffer)}
+}
+
+func (p *GenericBundlePackager) Export() ([]byte, error) {
+	// build a new zip file
+	buffer := bytes.NewBuffer([]byte{})
+	zipWriter := zip.NewWriter(buffer)
+	defer zipWriter.Close()
+
+	// write the manifest file
+	manifestFile, err := zipWriter.Create("manifest.yaml")
+	if err != nil {
+		return nil, err
+	}
+
+	manifestBytes := parser.MarshalYamlBytes(p.bundle)
+	_, err = manifestFile.Write(manifestBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	// write the assets
+	for name, asset := range p.assets {
+		assetFile, err := zipWriter.Create(name)
+		if err != nil {
+			return nil, err
+		}
+
+		_, err = assetFile.Write(asset.Bytes())
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// close the zip writer to flush the buffer
+	zipWriter.Close()
+
+	return buffer.Bytes(), nil
+}
+
+func (p *GenericBundlePackager) Manifest() (*bundle_entities.Bundle, error) {
+	return p.bundle, nil
+}
+
+func (p *GenericBundlePackager) Regenerate(bundle bundle_entities.Bundle) error {
+	// replace the basic information of the bundle
+	p.bundle.Author = bundle.Author
+	p.bundle.Description = bundle.Description
+	p.bundle.Name = bundle.Name
+	p.bundle.Labels = bundle.Labels
+
+	return nil
+}
+
+func (p *GenericBundlePackager) AppendGithubDependency(repoPattern bundle_entities.GithubRepoPattern) {
+	p.bundle.Dependencies = append(p.bundle.Dependencies, bundle_entities.Dependency{
+		Type: bundle_entities.DEPENDENCY_TYPE_GITHUB,
+		Value: bundle_entities.GithubDependency{
+			RepoPattern: repoPattern,
+		},
+	})
+}
+
+func (p *GenericBundlePackager) AppendMarketplaceDependency(marketplacePattern bundle_entities.MarketplacePattern) {
+	p.bundle.Dependencies = append(p.bundle.Dependencies, bundle_entities.Dependency{
+		Type: bundle_entities.DEPENDENCY_TYPE_MARKETPLACE,
+		Value: bundle_entities.MarketplaceDependency{
+			MarketplacePattern: marketplacePattern,
+		},
+	})
+}
+
+func (p *GenericBundlePackager) AppendPackageDependency(packagePath string) error {
+	// try to read the packagePath as a file
+	file, err := os.ReadFile(packagePath)
+	if err != nil {
+		return err
+	}
+
+	// try decode the file as a zip file
+	zipDecoder, err := decoder.NewZipPluginDecoder(file)
+	if err != nil {
+		return errors.Join(err, errors.New("please provider a valid difypkg file"))
+	}
+
+	checksum, err := zipDecoder.Checksum()
+	if err != nil {
+		return errors.Join(err, errors.New("failed to get checksum of the package"))
+	}
+
+	p.assets[checksum] = bytes.NewBuffer(file)
+	p.bundle.Dependencies = append(p.bundle.Dependencies, bundle_entities.Dependency{
+		Type: bundle_entities.DEPENDENCY_TYPE_PACKAGE,
+		Value: bundle_entities.PackageDependency{
+			Path: checksum,
+		},
+	})
+
+	return nil
+}
+
+func (p *GenericBundlePackager) ListDependencies() ([]bundle_entities.Dependency, error) {
+	return p.bundle.Dependencies, nil
+}
+
+func (p *GenericBundlePackager) Remove(index int) error {
+	if index < 0 || index >= len(p.bundle.Dependencies) {
+		return errors.New("index out of bounds")
+	}
+
+	// get the dependency
+	dependency := p.bundle.Dependencies[index]
+
+	// remove the asset
+	p.bundle.Dependencies = append(p.bundle.Dependencies[:index], p.bundle.Dependencies[index+1:]...)
+
+	// delete the asset
+	depValue, ok := dependency.Value.(bundle_entities.PackageDependency)
+	if ok {
+		delete(p.assets, depValue.Path)
+	}
+
+	return nil
+}
+
+func (p *GenericBundlePackager) BumpVersion(target manifest_entities.Version) {
+	p.bundle.Version = target
+}

+ 0 - 54
internal/core/bundle_packager/impl.go

@@ -1,54 +0,0 @@
-package bundle_packager
-
-import (
-	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
-	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/manifest_entities"
-)
-
-type BundlePackagerImpl struct {
-	bundle *bundle_entities.Bundle
-}
-
-func NewBundlePackager(zip []byte) BundlePackager {
-	return nil
-}
-
-func (p *BundlePackagerImpl) Export() ([]byte, error) {
-	return nil, nil
-}
-
-func (p *BundlePackagerImpl) Icon() ([]byte, error) {
-	return nil, nil
-}
-
-func (p *BundlePackagerImpl) Manifest() (*bundle_entities.Bundle, error) {
-	return p.bundle, nil
-}
-
-func (p *BundlePackagerImpl) Regenerate(bundle bundle_entities.Bundle) error {
-	return nil
-}
-
-func (p *BundlePackagerImpl) AppendGithubDependency(repoPattern bundle_entities.GithubRepoPattern) {
-
-}
-
-func (p *BundlePackagerImpl) AppendMarketplaceDependency(marketplacePattern bundle_entities.MarketplacePattern) {
-
-}
-
-func (p *BundlePackagerImpl) AppendPackageDependency(packagePath string) {
-
-}
-
-func (p *BundlePackagerImpl) ListDependencies() ([]bundle_entities.Dependency, error) {
-	return nil, nil
-}
-
-func (p *BundlePackagerImpl) Remove(index int) error {
-	return nil
-}
-
-func (p *BundlePackagerImpl) BumpVersion(target manifest_entities.Version) {
-
-}

+ 88 - 0
internal/core/bundle_packager/local.go

@@ -0,0 +1,88 @@
+package bundle_packager
+
+import (
+	"bytes"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
+)
+
+type LocalBundlePackager struct {
+	GenericBundlePackager
+
+	path string
+}
+
+func NewLocalBundlePackager(path string) (BundlePackager, error) {
+	// try read manifest file
+	manifestFile, err := os.Open(filepath.Join(path, "manifest.json"))
+	if err != nil {
+		return nil, err
+	}
+	defer manifestFile.Close()
+
+	manifestBytes, err := io.ReadAll(manifestFile)
+	if err != nil {
+		return nil, err
+	}
+
+	bundle, err := parser.UnmarshalYamlBytes[bundle_entities.Bundle](manifestBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	packager := &LocalBundlePackager{
+		GenericBundlePackager: *NewGenericBundlePackager(&bundle),
+		path:                  path,
+	}
+
+	// walk through the path and load the assets
+	err = filepath.Walk(filepath.Join(path, "_assets"), func(filePath string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if info.IsDir() {
+			return nil
+		}
+
+		assetBytes, err := os.ReadFile(filePath)
+		if err != nil {
+			return err
+		}
+
+		assetName := strings.TrimPrefix(filePath, path+"/_assets/")
+		packager.assets[assetName] = bytes.NewBuffer(assetBytes)
+
+		return nil
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	return packager, nil
+}
+
+func (p *LocalBundlePackager) Save() error {
+	// save the assets
+	for name, asset := range p.assets {
+		err := os.WriteFile(filepath.Join(p.path, "_assets", name), asset.Bytes(), 0644)
+		if err != nil {
+			return err
+		}
+	}
+
+	// save the manifest file
+	manifestBytes := parser.MarshalYamlBytes(p.bundle)
+	err := os.WriteFile(filepath.Join(p.path, "manifest.yaml"), manifestBytes, 0644)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 118 - 0
internal/core/bundle_packager/zip.go

@@ -0,0 +1,118 @@
+package bundle_packager
+
+import (
+	"archive/zip"
+	"bytes"
+	"io"
+	"os"
+	"strings"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
+)
+
+type ZipBundlePackager struct {
+	GenericBundlePackager
+
+	zipReader *zip.Reader
+	path      string
+}
+
+func NewZipBundlePackager(path string) (BundlePackager, error) {
+	zipFile, err := os.Open(path)
+	if err != nil {
+		return nil, err
+	}
+	defer zipFile.Close()
+
+	zipFileInfo, err := zipFile.Stat()
+	if err != nil {
+		return nil, err
+	}
+
+	zipReader, err := zip.NewReader(zipFile, zipFileInfo.Size())
+	if err != nil {
+		return nil, err
+	}
+
+	// try read manifest file
+	manifestFile, err := zipReader.Open("manifest.json")
+	if err != nil {
+		return nil, err
+	}
+	defer manifestFile.Close()
+
+	manifestBytes, err := io.ReadAll(manifestFile)
+	if err != nil {
+		return nil, err
+	}
+
+	bundle, err := parser.UnmarshalYamlBytes[bundle_entities.Bundle](manifestBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	packager := &ZipBundlePackager{
+		GenericBundlePackager: *NewGenericBundlePackager(&bundle),
+		zipReader:             zipReader,
+		path:                  path,
+	}
+
+	// walk through the zip file and load the assets
+	for _, file := range zipReader.File {
+		// if file starts with "assets/"
+		if strings.HasPrefix(file.Name, "assets/") {
+			// load the asset
+			asset, err := file.Open()
+			if err != nil {
+				return nil, err
+			}
+			defer asset.Close()
+
+			assetBytes, err := io.ReadAll(asset)
+			if err != nil {
+				return nil, err
+			}
+
+			// trim the prefix "assets/"
+			assetName := strings.TrimPrefix(file.Name, "assets/")
+
+			packager.assets[assetName] = bytes.NewBuffer(assetBytes)
+		}
+	}
+
+	return packager, nil
+}
+
+func (p *ZipBundlePackager) Save() error {
+	// export the bundle to a zip file
+	zipBytes, err := p.Export()
+	if err != nil {
+		return err
+	}
+
+	// save the zip file
+	err = os.WriteFile(p.path, zipBytes, 0644)
+	if err != nil {
+		return err
+	}
+
+	// reload zip reader
+	zipFile, err := os.Open(p.path)
+	if err != nil {
+		return err
+	}
+	defer zipFile.Close()
+
+	zipFileInfo, err := zipFile.Stat()
+	if err != nil {
+		return err
+	}
+
+	p.zipReader, err = zip.NewReader(zipFile, zipFileInfo.Size())
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

+ 65 - 0
internal/types/entities/bundle_entities/dependency.go

@@ -3,6 +3,7 @@ package bundle_entities
 import (
 	"fmt"
 	"regexp"
+	"strings"
 
 	"github.com/go-playground/validator/v10"
 	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/manifest_entities"
@@ -24,6 +25,39 @@ type Dependency struct {
 
 type GithubRepoPattern string
 
+func (p GithubRepoPattern) Split() []string {
+	return strings.Split(string(p), "/")
+}
+
+func (p GithubRepoPattern) Repo() string {
+	split := p.Split()
+	if len(split) < 3 {
+		return ""
+	}
+
+	organization, repo := split[0], split[1]
+
+	return fmt.Sprintf("https://github.com/%s/%s", organization, repo)
+}
+
+func (p GithubRepoPattern) Release() string {
+	split := p.Split()
+	if len(split) < 3 {
+		return ""
+	}
+
+	return split[2]
+}
+
+func (p GithubRepoPattern) Asset() string {
+	split := p.Split()
+	if len(split) < 4 {
+		return ""
+	}
+
+	return split[3]
+}
+
 func NewGithubRepoPattern(pattern string) (GithubRepoPattern, error) {
 	if !GITHUB_DEPENDENCY_PATTERN_REGEX_COMPILED.MatchString(pattern) {
 		return "", fmt.Errorf("invalid github repo pattern")
@@ -40,6 +74,37 @@ func NewMarketplacePattern(pattern string) (MarketplacePattern, error) {
 	return MarketplacePattern(pattern), nil
 }
 
+func (p MarketplacePattern) Split() []string {
+	return strings.Split(string(p), "/")
+}
+
+func (p MarketplacePattern) Organization() string {
+	split := p.Split()
+	if len(split) < 1 {
+		return ""
+	}
+
+	return split[0]
+}
+
+func (p MarketplacePattern) Plugin() string {
+	split := p.Split()
+	if len(split) < 2 {
+		return ""
+	}
+
+	return split[1]
+}
+
+func (p MarketplacePattern) Version() string {
+	split := p.Split()
+	if len(split) < 3 {
+		return ""
+	}
+
+	return split[2]
+}
+
 var (
 	GITHUB_VERSION_PATTERN = fmt.Sprintf(
 		`([~^]?%s|%s(\.%s){2}|%s-%s)`,

+ 8 - 0
internal/types/entities/manifest_entities/version.go

@@ -1,6 +1,7 @@
 package manifest_entities
 
 import (
+	"fmt"
 	"regexp"
 
 	"github.com/go-playground/validator/v10"
@@ -9,6 +10,13 @@ import (
 
 type Version string
 
+func NewVersion(version string) (Version, error) {
+	if !PluginDeclarationVersionRegex.MatchString(version) {
+		return "", fmt.Errorf("invalid version")
+	}
+	return Version(version), nil
+}
+
 func (v Version) String() string {
 	return string(v)
 }