289 lines
7 KiB
Go
289 lines
7 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/adrg/xdg"
|
|
)
|
|
|
|
const (
|
|
MASON_REGISTRY_RELEASE = "https://github.com/mason-org/mason-registry/releases/latest/download/registry.json.zip"
|
|
)
|
|
|
|
// LSPServer repräsentiert einen Language Server
|
|
type LSPServer struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Homepage string `json:"homepage"`
|
|
Languages []string `json:"languages"`
|
|
Categories []string `json:"categories"`
|
|
Executable string `json:"executable"`
|
|
InstallType string `json:"install_type"` // "github", "npm", "cargo", "go"
|
|
Source string `json:"source"` // URL oder Package-Name
|
|
Version string `json:"version"`
|
|
License string `json:"license"`
|
|
}
|
|
|
|
// Registry verwaltet alle verfügbaren LSP-Server
|
|
type Registry struct {
|
|
Servers []LSPServer `json:"servers"`
|
|
CachePath string
|
|
LastUpdated time.Time
|
|
}
|
|
|
|
// MasonPackage entspricht der Mason-Registry Struktur
|
|
type MasonPackage struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Homepage string `json:"homepage"`
|
|
Licenses []string `json:"licenses"`
|
|
Languages []string `json:"languages"`
|
|
Categories []string `json:"categories"`
|
|
Source struct {
|
|
ID string `json:"id"`
|
|
} `json:"source"`
|
|
Bin map[string]string `json:"bin,omitempty"`
|
|
}
|
|
|
|
// LoadRegistry lädt die Registry aus einer JSON-Datei oder von Mason
|
|
func LoadRegistry(path string) (*Registry, error) {
|
|
// Prüfe ob path "mason" ist -> dann von GitHub laden
|
|
if path == "mason" {
|
|
return LoadRegistryFromMason()
|
|
}
|
|
|
|
// Ansonsten aus lokaler Datei laden
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
data, err := io.ReadAll(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var registry Registry
|
|
if err := json.Unmarshal(data, ®istry); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ®istry, nil
|
|
}
|
|
|
|
// LoadRegistryFromMason lädt die Registry von Mason mit Cache
|
|
func LoadRegistryFromMason() (*Registry, error) {
|
|
cacheDir := filepath.Join(xdg.CacheHome, "lsp-manager")
|
|
os.MkdirAll(cacheDir, 0755)
|
|
cachePath := filepath.Join(cacheDir, "mason-registry.json")
|
|
|
|
registry := &Registry{
|
|
CachePath: cachePath,
|
|
}
|
|
|
|
// Prüfe ob Cache existiert und aktuell ist (< 24h alt)
|
|
if info, err := os.Stat(cachePath); err == nil {
|
|
if time.Since(info.ModTime()) < 24*time.Hour {
|
|
fmt.Println("📦 Loading registry from cache...")
|
|
if err := registry.loadFromCache(); err == nil {
|
|
return registry, nil
|
|
}
|
|
fmt.Printf("Cache load failed, downloading fresh copy...\n")
|
|
}
|
|
}
|
|
|
|
// Cache ist veraltet oder existiert nicht, lade von GitHub
|
|
if err := registry.downloadFromMason(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return registry, nil
|
|
}
|
|
|
|
// loadFromCache lädt die Registry aus dem lokalen Cache
|
|
func (r *Registry) loadFromCache() error {
|
|
data, err := os.ReadFile(r.CachePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read cache: %w", err)
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &r.Servers); err != nil {
|
|
return fmt.Errorf("failed to parse cache: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// downloadFromMason lädt die Mason-Registry von GitHub
|
|
func (r *Registry) downloadFromMason() error {
|
|
fmt.Println("📦 Downloading Mason registry from GitHub...")
|
|
|
|
resp, err := http.Get(MASON_REGISTRY_RELEASE)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to download registry: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
}
|
|
|
|
// Lese ZIP-Daten in Speicher
|
|
zipData, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read registry data: %w", err)
|
|
}
|
|
|
|
// Entpacke ZIP
|
|
zipReader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open zip: %w", err)
|
|
}
|
|
|
|
// Finde registry.json in ZIP
|
|
var registryJSON []byte
|
|
for _, file := range zipReader.File {
|
|
if file.Name == "registry.json" {
|
|
f, err := file.Open()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open registry.json: %w", err)
|
|
}
|
|
registryJSON, err = io.ReadAll(f)
|
|
f.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read registry.json: %w", err)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if registryJSON == nil {
|
|
return fmt.Errorf("registry.json not found in archive")
|
|
}
|
|
|
|
// Parse Mason-Format: registry.json ist ein Array
|
|
var masonPackages []MasonPackage
|
|
if err := json.Unmarshal(registryJSON, &masonPackages); err != nil {
|
|
return fmt.Errorf("failed to parse registry: %w", err)
|
|
}
|
|
|
|
// Konvertiere zu unserem Format
|
|
r.Servers = convertMasonToLSPServers(masonPackages)
|
|
|
|
fmt.Printf("✅ Registry updated with %d packages (%d LSP servers)\n",
|
|
len(masonPackages), len(r.Servers))
|
|
|
|
// Speichere konvertierte Servers im Cache
|
|
if cacheData, err := json.MarshalIndent(r.Servers, "", " "); err == nil {
|
|
if err := os.WriteFile(r.CachePath, cacheData, 0644); err != nil {
|
|
fmt.Printf("⚠️ Warning: failed to cache registry: %v\n", err)
|
|
}
|
|
}
|
|
|
|
r.LastUpdated = time.Now()
|
|
return nil
|
|
}
|
|
|
|
func convertMasonToLSPServers(masonPackages []MasonPackage) []LSPServer {
|
|
servers := make([]LSPServer, 0, len(masonPackages))
|
|
|
|
for _, pkg := range masonPackages {
|
|
// Filtere nur LSP-Server
|
|
isLSP := false
|
|
for _, cat := range pkg.Categories {
|
|
if cat == "LSP" {
|
|
isLSP = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !isLSP {
|
|
continue
|
|
}
|
|
|
|
// Bestimme Executable-Namen
|
|
executable := pkg.Name
|
|
if len(pkg.Bin) > 0 {
|
|
for _, binName := range pkg.Bin {
|
|
executable = binName
|
|
break
|
|
}
|
|
}
|
|
|
|
// Konvertiere Source-ID zu unserem Format
|
|
installType, source := parseMasonSourceID(pkg.Source.ID)
|
|
|
|
servers = append(servers, LSPServer{
|
|
Name: pkg.Name,
|
|
Description: pkg.Description,
|
|
Homepage: pkg.Homepage,
|
|
Languages: pkg.Languages,
|
|
Categories: pkg.Categories,
|
|
Executable: executable,
|
|
InstallType: installType,
|
|
Source: source,
|
|
Version: "latest",
|
|
License: getLicense(pkg.Licenses),
|
|
})
|
|
}
|
|
|
|
return servers
|
|
}
|
|
|
|
// parseMasonSourceID extrahiert InstallType und Source aus Mason Source-ID
|
|
// Format: "pkg:github/owner/repo", "pkg:npm/package-name", etc.
|
|
func parseMasonSourceID(sourceID string) (installType, source string) {
|
|
if !strings.HasPrefix(sourceID, "pkg:") {
|
|
return "unknown", sourceID
|
|
}
|
|
|
|
// Entferne "pkg:" Prefix
|
|
parts := strings.SplitN(sourceID[4:], "/", 2)
|
|
if len(parts) < 2 {
|
|
return "unknown", sourceID
|
|
}
|
|
|
|
installType = parts[0]
|
|
source = parts[1]
|
|
|
|
return installType, source
|
|
}
|
|
|
|
// getLicense gibt die erste Lizenz zurück oder "Unknown"
|
|
func getLicense(licenses []string) string {
|
|
if len(licenses) > 0 {
|
|
return licenses[0]
|
|
}
|
|
return "Unknown"
|
|
}
|
|
|
|
// LoadRegistryFromURL lädt von einer Remote-URL (Fallback)
|
|
func LoadRegistryFromURL(url string) (*Registry, error) {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var registry Registry
|
|
if err := json.Unmarshal(data, ®istry); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ®istry, nil
|
|
}
|