Skip to content

Commit 128b59a

Browse files
committed
feat: Implement plugin availability checks for pre-built binaries and bulk availability
1 parent 9065a1b commit 128b59a

4 files changed

Lines changed: 431 additions & 109 deletions

File tree

app/plugins/plugin_installer.go

Lines changed: 151 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,52 +1801,99 @@ func extractPluginIDFromRepo(repo string) string {
18011801
// Pre-built Binary Installation
18021802
// =============================================================================
18031803

1804-
// getPlatformString returns the platform identifier for the current system
1805-
func getPlatformString() string {
1806-
goos := runtime.GOOS
1807-
goarch := runtime.GOARCH
1804+
// PluginAvailability represents the available installation options for a plugin
1805+
type PluginAvailability struct {
1806+
PluginID string `json:"pluginId"`
1807+
Repository string `json:"repository"`
1808+
Platform string `json:"platform"`
1809+
HasStable bool `json:"hasStable"`
1810+
StableVersion string `json:"stableVersion,omitempty"`
1811+
StableSize int64 `json:"stableSize,omitempty"`
1812+
HasBeta bool `json:"hasBeta"`
1813+
BetaVersion string `json:"betaVersion,omitempty"`
1814+
BetaSize int64 `json:"betaSize,omitempty"`
1815+
CanBuildSource bool `json:"canBuildSource"`
1816+
Error string `json:"error,omitempty"`
1817+
}
18081818

1809-
if goos != "linux" {
1810-
return "" // Only Linux pre-built binaries are available
1819+
// CheckPluginAvailability checks what installation options are available for a plugin
1820+
func (pi *PluginInstaller) CheckPluginAvailability(org, repo string) PluginAvailability {
1821+
pluginID := repo
1822+
if strings.HasPrefix(repo, "Plugin_") {
1823+
pluginID = strings.TrimPrefix(repo, "Plugin_")
18111824
}
18121825

1813-
switch goarch {
1814-
case "amd64":
1815-
return "linux-amd64"
1816-
case "386":
1817-
return "linux-386"
1818-
case "arm64":
1819-
return "linux-arm64"
1820-
case "arm":
1821-
// Detect ARM version - default to ARM6 for compatibility
1822-
return "linux-arm6"
1823-
default:
1824-
return ""
1826+
platform := getPlatformString()
1827+
result := PluginAvailability{
1828+
PluginID: pluginID,
1829+
Repository: fmt.Sprintf("https://github.com/%s/%s", org, repo),
1830+
Platform: platform,
1831+
CanBuildSource: true, // Always available as fallback
18251832
}
1826-
}
18271833

1828-
// GitHubRelease represents a GitHub release from the API
1829-
type GitHubRelease struct {
1830-
TagName string `json:"tag_name"`
1831-
Name string `json:"name"`
1832-
Prerelease bool `json:"prerelease"`
1833-
Assets []GitHubReleaseAsset `json:"assets"`
1834-
}
1834+
if platform == "" {
1835+
result.Error = fmt.Sprintf("No pre-built binaries for %s/%s, source install only", runtime.GOOS, runtime.GOARCH)
1836+
return result
1837+
}
18351838

1836-
// GitHubReleaseAsset represents an asset in a GitHub release
1837-
type GitHubReleaseAsset struct {
1838-
Name string `json:"name"`
1839-
BrowserDownloadURL string `json:"browser_download_url"`
1840-
Size int64 `json:"size"`
1839+
// Check stable release
1840+
stableRelease, stableAsset, err := pi.findReleaseAsset(org, repo, pluginID, platform, false)
1841+
if err == nil && stableAsset != nil {
1842+
result.HasStable = true
1843+
result.StableVersion = stableRelease.TagName
1844+
result.StableSize = stableAsset.Size
1845+
}
1846+
1847+
// Check beta release
1848+
betaRelease, betaAsset, err := pi.findReleaseAsset(org, repo, pluginID, platform, true)
1849+
if err == nil && betaAsset != nil {
1850+
result.HasBeta = true
1851+
result.BetaVersion = betaRelease.TagName
1852+
result.BetaSize = betaAsset.Size
1853+
}
1854+
1855+
return result
18411856
}
18421857

1843-
// downloadPrebuiltBinary downloads and installs a pre-built plugin binary from GitHub releases
1844-
func (pi *PluginInstaller) downloadPrebuiltBinary(org, repo, pluginID string, useBeta bool) error {
1845-
platform := getPlatformString()
1846-
if platform == "" {
1847-
return fmt.Errorf("no pre-built binary available for this platform: %s/%s", runtime.GOOS, runtime.GOARCH)
1858+
// CheckMultiplePluginsAvailability checks availability for multiple plugins
1859+
func (pi *PluginInstaller) CheckMultiplePluginsAvailability(repositories []string) []PluginAvailability {
1860+
results := make([]PluginAvailability, 0, len(repositories))
1861+
1862+
for _, repo := range repositories {
1863+
// Extract org and repo name from the URL
1864+
parts := strings.Split(repo, "/")
1865+
var org, repoName string
1866+
for i, part := range parts {
1867+
if part == "github.com" && i+2 < len(parts) {
1868+
org = parts[i+1]
1869+
repoName = parts[i+2]
1870+
break
1871+
}
1872+
}
1873+
1874+
if org == "" || repoName == "" {
1875+
// Fallback: use last two parts
1876+
if len(parts) >= 2 {
1877+
org = parts[len(parts)-2]
1878+
repoName = parts[len(parts)-1]
1879+
}
1880+
}
1881+
1882+
// Remove .git suffix if present
1883+
if strings.HasSuffix(repoName, ".git") {
1884+
repoName = repoName[:len(repoName)-4]
1885+
}
1886+
1887+
if org != "" && repoName != "" {
1888+
results = append(results, pi.CheckPluginAvailability(org, repoName))
1889+
}
18481890
}
18491891

1892+
return results
1893+
}
1894+
1895+
// findReleaseAsset finds the appropriate asset for the platform in a release
1896+
func (pi *PluginInstaller) findReleaseAsset(org, repo, pluginID, platform string, useBeta bool) (*GitHubRelease, *GitHubReleaseAsset, error) {
18501897
// Determine which release to fetch
18511898
var releaseURL string
18521899
if useBeta {
@@ -1858,7 +1905,7 @@ func (pi *PluginInstaller) downloadPrebuiltBinary(org, repo, pluginID string, us
18581905
// Create request
18591906
req, err := http.NewRequest("GET", releaseURL, nil)
18601907
if err != nil {
1861-
return fmt.Errorf("failed to create request: %v", err)
1908+
return nil, nil, fmt.Errorf("failed to create request: %v", err)
18621909
}
18631910

18641911
// Add authentication if available
@@ -1873,51 +1920,95 @@ func (pi *PluginInstaller) downloadPrebuiltBinary(org, repo, pluginID string, us
18731920
client := &http.Client{Timeout: 30 * time.Second}
18741921
resp, err := client.Do(req)
18751922
if err != nil {
1876-
return fmt.Errorf("failed to fetch release info: %v", err)
1923+
return nil, nil, fmt.Errorf("failed to fetch release info: %v", err)
18771924
}
18781925
defer resp.Body.Close()
18791926

18801927
if resp.StatusCode != http.StatusOK {
1881-
return fmt.Errorf("release not found (HTTP %d)", resp.StatusCode)
1928+
return nil, nil, fmt.Errorf("release not found (HTTP %d)", resp.StatusCode)
18821929
}
18831930

18841931
// Parse release
18851932
var release GitHubRelease
18861933
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
1887-
return fmt.Errorf("failed to parse release info: %v", err)
1934+
return nil, nil, fmt.Errorf("failed to parse release info: %v", err)
18881935
}
18891936

18901937
// Find the appropriate asset for this platform
1891-
var assetURL string
1892-
var assetName string
1893-
versionSuffix := release.TagName
1894-
if useBeta {
1895-
versionSuffix = "beta"
1938+
for i, asset := range release.Assets {
1939+
// Try various naming patterns
1940+
if strings.Contains(asset.Name, platform) && strings.HasSuffix(asset.Name, ".tar.gz") {
1941+
return &release, &release.Assets[i], nil
1942+
}
18961943
}
18971944

1898-
expectedName := fmt.Sprintf("%s-%s-%s.tar.gz", pluginID, platform, versionSuffix)
1945+
return &release, nil, fmt.Errorf("no asset found for platform %s", platform)
1946+
}
18991947

1900-
for _, asset := range release.Assets {
1901-
if asset.Name == expectedName {
1902-
assetURL = asset.BrowserDownloadURL
1903-
assetName = asset.Name
1904-
break
1905-
}
1906-
// Also try without version suffix for beta
1907-
if useBeta && strings.Contains(asset.Name, platform) && strings.HasSuffix(asset.Name, ".tar.gz") {
1908-
assetURL = asset.BrowserDownloadURL
1909-
assetName = asset.Name
1910-
}
1948+
// GetPlatformString exports the platform string for API use
1949+
func GetPlatformString() string {
1950+
return getPlatformString()
1951+
}
1952+
1953+
// getPlatformString returns the platform identifier for the current system
1954+
func getPlatformString() string {
1955+
goos := runtime.GOOS
1956+
goarch := runtime.GOARCH
1957+
1958+
if goos != "linux" {
1959+
return "" // Only Linux pre-built binaries are available
19111960
}
19121961

1913-
if assetURL == "" {
1962+
switch goarch {
1963+
case "amd64":
1964+
return "linux-amd64"
1965+
case "386":
1966+
return "linux-386"
1967+
case "arm64":
1968+
return "linux-arm64"
1969+
case "arm":
1970+
// Detect ARM version - default to ARM6 for compatibility
1971+
return "linux-arm6"
1972+
default:
1973+
return ""
1974+
}
1975+
}
1976+
1977+
// GitHubRelease represents a GitHub release from the API
1978+
type GitHubRelease struct {
1979+
TagName string `json:"tag_name"`
1980+
Name string `json:"name"`
1981+
Prerelease bool `json:"prerelease"`
1982+
Assets []GitHubReleaseAsset `json:"assets"`
1983+
}
1984+
1985+
// GitHubReleaseAsset represents an asset in a GitHub release
1986+
type GitHubReleaseAsset struct {
1987+
Name string `json:"name"`
1988+
BrowserDownloadURL string `json:"browser_download_url"`
1989+
Size int64 `json:"size"`
1990+
}
1991+
1992+
// downloadPrebuiltBinary downloads and installs a pre-built plugin binary from GitHub releases
1993+
func (pi *PluginInstaller) downloadPrebuiltBinary(org, repo, pluginID string, useBeta bool) error {
1994+
platform := getPlatformString()
1995+
if platform == "" {
1996+
return fmt.Errorf("no pre-built binary available for this platform: %s/%s", runtime.GOOS, runtime.GOARCH)
1997+
}
1998+
1999+
// Use the new findReleaseAsset function
2000+
release, asset, err := pi.findReleaseAsset(org, repo, pluginID, platform, useBeta)
2001+
if err != nil {
2002+
return err
2003+
}
2004+
if asset == nil {
19142005
return fmt.Errorf("no binary found for platform %s in release %s", platform, release.TagName)
19152006
}
19162007

1917-
log.Printf("Downloading pre-built binary: %s", assetName)
2008+
log.Printf("Downloading pre-built binary: %s", asset.Name)
19182009

19192010
// Download the asset
1920-
assetResp, err := http.Get(assetURL)
2011+
assetResp, err := http.Get(asset.BrowserDownloadURL)
19212012
if err != nil {
19222013
return fmt.Errorf("failed to download binary: %v", err)
19232014
}

frontend/src/api/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ export const pluginManagerApi = {
124124
// Refresh plugin catalog from GitHub
125125
refreshCatalog: () => api.post('/plugins/manage/refresh-catalog'),
126126

127+
// Check if prebuilt binaries are available for a plugin
128+
checkAvailability: (repository) => api.post('/plugins/manage/check-availability', { repository }),
129+
130+
// Check availability for multiple plugins
131+
checkAvailabilityBulk: (repositories) => api.post('/plugins/manage/check-availability-bulk', { repositories }),
132+
127133
// Install plugin from repository with channel selection
128134
// channel: 'stable' (default), 'beta', or 'source'
129135
install: (repository, channel = 'stable') => api.post('/plugins/manage/install', { repository, channel }),

0 commit comments

Comments
 (0)