Files
CLIProxyAPI/sdk/pluginstore/pluginstore.go
2026-06-22 22:25:44 +08:00

173 lines
6.1 KiB
Go

// Package pluginstore exposes plugin registry and artifact installation helpers
// for embedders such as CLIProxyAPIHome.
package pluginstore
import (
"context"
"fmt"
"net/http"
"strings"
internalpluginstore "github.com/router-for-me/CLIProxyAPI/v7/internal/pluginstore"
)
const (
DefaultRegistryURL = internalpluginstore.DefaultRegistryURL
DefaultSourceID = internalpluginstore.DefaultSourceID
DefaultSourceName = internalpluginstore.DefaultSourceName
SchemaVersion = internalpluginstore.SchemaVersion
)
type Source = internalpluginstore.Source
type Registry = internalpluginstore.Registry
type Plugin = internalpluginstore.Plugin
type Release = internalpluginstore.Release
type ReleaseAsset = internalpluginstore.ReleaseAsset
type InstallOptions = internalpluginstore.InstallOptions
type InstallResult = internalpluginstore.InstallResult
type HTTPDoer interface {
Do(*http.Request) (*http.Response, error)
}
var ErrLoadedPluginLocked = internalpluginstore.ErrLoadedPluginLocked
type Client struct {
inner internalpluginstore.Client
}
type Manifest struct {
ID string `yaml:"id,omitempty" json:"id,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Author string `yaml:"author,omitempty" json:"author,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
ReleaseTag string `yaml:"release-tag,omitempty" json:"release_tag,omitempty"`
Repository string `yaml:"repository,omitempty" json:"repository,omitempty"`
Logo string `yaml:"logo,omitempty" json:"logo,omitempty"`
Homepage string `yaml:"homepage,omitempty" json:"homepage,omitempty"`
License string `yaml:"license,omitempty" json:"license,omitempty"`
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`
SourceID string `yaml:"source-id,omitempty" json:"source_id,omitempty"`
SourceName string `yaml:"source-name,omitempty" json:"source_name,omitempty"`
SourceURL string `yaml:"source-url,omitempty" json:"source_url,omitempty"`
}
func NewClient(httpClient HTTPDoer, registryURL string) Client {
return Client{inner: internalpluginstore.Client{
HTTPClient: httpClient,
RegistryURL: strings.TrimSpace(registryURL),
}}
}
func DefaultSource() Source {
return internalpluginstore.DefaultSource()
}
func NormalizeSources(registryURLs []string) ([]Source, error) {
return internalpluginstore.NormalizeSources(registryURLs)
}
func SourceID(registryURL string) string {
return internalpluginstore.SourceID(registryURL)
}
func ValidatePlugin(plugin Plugin) error {
return internalpluginstore.ValidatePlugin(plugin)
}
func UpdateAvailable(installed, latest string) bool {
return internalpluginstore.UpdateAvailable(installed, latest)
}
func ReleaseVersion(release Release) (string, error) {
return internalpluginstore.ReleaseVersion(release)
}
func ManifestFromRelease(source Source, plugin Plugin, release Release) (Manifest, error) {
version, errVersion := internalpluginstore.ReleaseVersion(release)
if errVersion != nil {
return Manifest{}, errVersion
}
return Manifest{
ID: strings.TrimSpace(plugin.ID),
Name: strings.TrimSpace(plugin.Name),
Description: strings.TrimSpace(plugin.Description),
Author: strings.TrimSpace(plugin.Author),
Version: version,
ReleaseTag: strings.TrimSpace(release.TagName),
Repository: strings.TrimSpace(plugin.Repository),
Logo: strings.TrimSpace(plugin.Logo),
Homepage: strings.TrimSpace(plugin.Homepage),
License: strings.TrimSpace(plugin.License),
Tags: append([]string(nil), plugin.Tags...),
SourceID: strings.TrimSpace(source.ID),
SourceName: strings.TrimSpace(source.Name),
SourceURL: strings.TrimSpace(source.URL),
}, nil
}
func (m Manifest) Plugin() Plugin {
return Plugin{
ID: strings.TrimSpace(m.ID),
Name: strings.TrimSpace(m.Name),
Description: strings.TrimSpace(m.Description),
Author: strings.TrimSpace(m.Author),
Version: strings.TrimSpace(m.Version),
Repository: strings.TrimSpace(m.Repository),
Logo: strings.TrimSpace(m.Logo),
Homepage: strings.TrimSpace(m.Homepage),
License: strings.TrimSpace(m.License),
Tags: append([]string(nil), m.Tags...),
}
}
func (m Manifest) Validate() error {
version := strings.TrimSpace(m.Version)
if version == "" {
return fmt.Errorf("missing required field version")
}
releaseTag := strings.TrimSpace(m.ReleaseTag)
if releaseTag == "" {
return fmt.Errorf("missing required field release-tag")
}
if errValidate := internalpluginstore.ValidatePlugin(m.Plugin()); errValidate != nil {
return errValidate
}
releaseVersion, errVersion := internalpluginstore.ReleaseVersion(internalpluginstore.Release{TagName: releaseTag})
if errVersion != nil {
return errVersion
}
if releaseVersion != version {
return fmt.Errorf("release-tag %q resolves version %q, want %q", releaseTag, releaseVersion, version)
}
return nil
}
func (c Client) FetchRegistry(ctx context.Context) (Registry, error) {
return c.inner.FetchRegistry(ctx)
}
func (c Client) FetchLatestRelease(ctx context.Context, plugin Plugin) (Release, error) {
return c.inner.FetchLatestRelease(ctx, plugin)
}
func (c Client) FetchReleaseByTag(ctx context.Context, plugin Plugin, tag string) (Release, error) {
return c.inner.FetchReleaseByTag(ctx, plugin, tag)
}
func (c Client) Install(ctx context.Context, plugin Plugin, options InstallOptions) (InstallResult, error) {
return c.inner.Install(ctx, plugin, options)
}
func (c Client) InstallVersion(ctx context.Context, plugin Plugin, releaseTag string, version string, options InstallOptions) (InstallResult, error) {
return c.inner.InstallVersion(ctx, plugin, releaseTag, version, options)
}
func (c Client) InstallManifest(ctx context.Context, manifest Manifest, options InstallOptions) (InstallResult, error) {
if errValidate := manifest.Validate(); errValidate != nil {
return InstallResult{}, errValidate
}
return c.InstallVersion(ctx, manifest.Plugin(), manifest.ReleaseTag, manifest.Version, options)
}