164 lines
5.1 KiB
Go
164 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/wailsapp/wails/v2"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
|
|
|
"github.com/verstak/verstak-desktop/internal/api"
|
|
"github.com/verstak/verstak-desktop/internal/core/capability"
|
|
"github.com/verstak/verstak-desktop/internal/core/contribution"
|
|
"github.com/verstak/verstak-desktop/internal/core/events"
|
|
"github.com/verstak/verstak-desktop/internal/core/permissions"
|
|
"github.com/verstak/verstak-desktop/internal/core/plugin"
|
|
)
|
|
|
|
//go:embed frontend/dist
|
|
var assets embed.FS
|
|
|
|
// expandPath resolves "~" to the user's home directory.
|
|
func expandPath(path string) string {
|
|
if strings.HasPrefix(path, "~/") {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
log.Printf("[main] expandPath: cannot get home dir: %v", err)
|
|
return path
|
|
}
|
|
return filepath.Join(home, path[2:])
|
|
}
|
|
return path
|
|
}
|
|
|
|
func main() {
|
|
// ─── Initialize Core Registries ──────────────────────────
|
|
capRegistry := capability.NewRegistry()
|
|
contribRegistry := contribution.NewRegistry()
|
|
permRegistry := permissions.NewRegistry()
|
|
eventBus := events.NewBus()
|
|
|
|
// ─── Register Core Capabilities ─────────────────────────
|
|
// These are provided by the desktop core itself, not by plugins.
|
|
// Registered before plugin discovery so that plugins can resolve
|
|
// required capabilities (e.g. verstak/core/plugin-manager/v1) at load time.
|
|
corePluginID := "verstak-desktop"
|
|
coreCaps := []string{
|
|
"verstak/core/plugin-manager/v1",
|
|
"verstak/core/capability-registry/v1",
|
|
"verstak/core/contribution-registry/v1",
|
|
"verstak/core/permissions/v1",
|
|
"verstak/core/events/v1",
|
|
}
|
|
if err := capRegistry.Register(corePluginID, coreCaps); err != nil {
|
|
log.Fatalf("[main] failed to register core capabilities: %v", err)
|
|
}
|
|
log.Printf("[main] registered %d core capabilities", len(coreCaps))
|
|
|
|
// ─── Plugin Discovery ───────────────────────────────────
|
|
discoveryDirs := []string{
|
|
"~/.config/verstak/plugins",
|
|
"./plugins",
|
|
}
|
|
|
|
// Expand tilde in all paths
|
|
for i, d := range discoveryDirs {
|
|
discoveryDirs[i] = expandPath(d)
|
|
}
|
|
|
|
log.Printf("[main] plugin dirs: %v", discoveryDirs)
|
|
|
|
plugins, discErrors := plugin.DiscoverPlugins(discoveryDirs)
|
|
for _, err := range discErrors {
|
|
log.Printf("[plugin] discovery warning: %v", err)
|
|
}
|
|
|
|
log.Printf("[plugin] discovered %d plugins", len(plugins))
|
|
|
|
// ─── Plugin Lifecycle: Register Capabilities + Contributions ──
|
|
for i := range plugins {
|
|
p := &plugins[i]
|
|
|
|
// Register provided capabilities
|
|
if len(p.Manifest.Provides) > 0 {
|
|
if err := capRegistry.Register(p.Manifest.ID, p.Manifest.Provides); err != nil {
|
|
log.Printf("[plugin] %s: capability registration failed: %v", p.Manifest.ID, err)
|
|
p.Status = plugin.StatusFailed
|
|
p.Error = err.Error()
|
|
continue
|
|
}
|
|
log.Printf("[plugin] %s: registered %d capabilities", p.Manifest.ID, len(p.Manifest.Provides))
|
|
}
|
|
|
|
// Resolve required capabilities
|
|
missingRequired := capRegistry.CheckRequired(p.Manifest.Requires)
|
|
if len(missingRequired) > 0 {
|
|
log.Printf("[plugin] %s: missing required capabilities: %v", p.Manifest.ID, missingRequired)
|
|
p.Status = plugin.StatusMissingRequiredCapability
|
|
p.Error = fmt.Sprintf("missing required: %s", strings.Join(missingRequired, ", "))
|
|
continue
|
|
}
|
|
|
|
// Check optional capabilities for degraded mode
|
|
missingOptional := capRegistry.CheckRequired(p.Manifest.OptionalRequires)
|
|
if len(missingOptional) > 0 {
|
|
log.Printf("[plugin] %s: missing optional capabilities (degraded): %v", p.Manifest.ID, missingOptional)
|
|
p.Status = plugin.StatusDegraded
|
|
} else {
|
|
p.Status = plugin.StatusLoaded
|
|
}
|
|
|
|
// Register contributions
|
|
if p.Manifest.Contributes != nil {
|
|
contribRegistry.Register(p.Manifest.ID, p.Manifest.Contributes)
|
|
log.Printf("[plugin] %s: contributions registered", p.Manifest.ID)
|
|
}
|
|
|
|
log.Printf("[plugin] %s: status=%s", p.Manifest.ID, p.Status)
|
|
}
|
|
|
|
// ─── Log Summary ───────────────────────────────────────
|
|
loaded := 0
|
|
degraded := 0
|
|
failed := 0
|
|
for _, p := range plugins {
|
|
switch p.Status {
|
|
case plugin.StatusLoaded:
|
|
loaded++
|
|
case plugin.StatusDegraded:
|
|
degraded++
|
|
default:
|
|
failed++
|
|
}
|
|
}
|
|
log.Printf("[main] lifecycle summary: loaded=%d degraded=%d failed=%d", loaded, degraded, failed)
|
|
|
|
// Create the App struct
|
|
app := api.NewApp(capRegistry, contribRegistry, permRegistry, eventBus, plugins)
|
|
|
|
// ─── Wails App ───────────────────────────────────────────
|
|
err := wails.Run(&options.App{
|
|
Title: "Verstak",
|
|
Width: 1200,
|
|
Height: 800,
|
|
MinWidth: 800,
|
|
MinHeight: 600,
|
|
WindowStartState: options.Normal,
|
|
AssetServer: &assetserver.Options{
|
|
Assets: assets,
|
|
},
|
|
Bind: []interface{}{
|
|
app,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
log.Fatalf("Error: %v", err)
|
|
}
|
|
}
|