fix: only plugins with on_install are managed - skip others entirely

- Discover(): skip plugins without on_install hook (not shown in UI)
- Enable(): simplified - just check Installed flag
- SyncConfig(): simplified - no HasInstall special case
- Frontend: simplified toggle logic (only installed/uninstalled states)
- Tests: updated all test fixtures to include on_install hook
This commit is contained in:
mirivlad 2026-06-07 15:55:04 +08:00
parent 5c769c92a0
commit 3f787ec66d
4 changed files with 30 additions and 35 deletions

View File

@ -107,11 +107,11 @@
</div> </div>
</div> </div>
<div class="plugin-toggle"> <div class="plugin-toggle">
{#if p.hasInstall && !p.installed} {#if !p.installed}
<button class="install-btn" on:click={() => install(p)}> <button class="install-btn" on:click={() => install(p)}>
📦 Установить 📦 Установить
</button> </button>
{:else if p.hasInstall && p.installed} {:else}
<div class="toggle-group"> <div class="toggle-group">
<button <button
class="toggle-btn" class="toggle-btn"
@ -126,16 +126,6 @@
🗑 🗑
</button> </button>
</div> </div>
{:else}
<button
class="toggle-btn"
class:active={p.active}
on:click={() => toggle(p)}
role="switch"
aria-checked={p.active}
>
<span class="toggle-knob"></span>
</button>
{/if} {/if}
</div> </div>
</div> </div>

View File

@ -130,14 +130,18 @@ func (m *Manager) Discover() {
os.MkdirAll(dataDir, 0o750) os.MkdirAll(dataDir, 0o750)
hasInstall := meta.Hooks["on_install"] != "" hasInstall := meta.Hooks["on_install"] != ""
if !hasInstall {
log.Printf("[plugins] %s: skipping — no on_install hook (not a managed plugin)", e.Name())
continue
}
m.plugins = append(m.plugins, Plugin{ m.plugins = append(m.plugins, Plugin{
Meta: meta, Meta: meta,
Dir: filepath.Join(pluginsDir, e.Name()), Dir: filepath.Join(pluginsDir, e.Name()),
DataDir: dataDir, DataDir: dataDir,
Active: false, Active: false,
Installed: false, Installed: false,
HasInstall: hasInstall, HasInstall: true,
}) })
} }
} }
@ -158,10 +162,6 @@ func (m *Manager) SyncConfig(cfg *config.AppConfig) {
} }
for i := range m.plugins { for i := range m.plugins {
installed := installedSet[m.plugins[i].Meta.Name] installed := installedSet[m.plugins[i].Meta.Name]
// Plugins without on_install hook are always "installed"
if !m.plugins[i].HasInstall {
installed = true
}
m.plugins[i].Installed = installed m.plugins[i].Installed = installed
m.plugins[i].Active = installed && enabledSet[m.plugins[i].Meta.Name] m.plugins[i].Active = installed && enabledSet[m.plugins[i].Meta.Name]
} }
@ -352,16 +352,14 @@ type NodeMeta struct {
Type string `json:"type"` // text, url, etc. Type string `json:"type"` // text, url, etc.
} }
// Enable activates a plugin by name. // Enable activates a plugin by name. Plugin must be installed first.
// If the plugin has on_install hook, it must be installed first.
func (m *Manager) Enable(name string) error { func (m *Manager) Enable(name string) error {
for i := range m.plugins { for i := range m.plugins {
if m.plugins[i].Meta.Name == name { if m.plugins[i].Meta.Name == name {
p := &m.plugins[i] if !m.plugins[i].Installed {
if p.HasInstall && !p.Installed {
return fmt.Errorf("plugin %q must be installed first", name) return fmt.Errorf("plugin %q must be installed first", name)
} }
p.Active = true m.plugins[i].Active = true
return nil return nil
} }
} }

View File

@ -41,7 +41,8 @@ func TestDiscover(t *testing.T) {
"plugin.json": []byte(`{ "plugin.json": []byte(`{
"name": "client", "name": "client",
"version": "1.0.0", "version": "1.0.0",
"description": "Client template", "description": "Client plugin",
"hooks": { "on_install": "on_install" },
"templates": ["client"] "templates": ["client"]
}`), }`),
}, },
@ -78,7 +79,8 @@ func TestDiscover(t *testing.T) {
t.Errorf("plugin name = %q", plugins[0].Meta.Name) t.Errorf("plugin name = %q", plugins[0].Meta.Name)
} }
// Enable plugin to load templates // Install & enable plugin to load templates
mgr.Install("client")
mgr.Enable("client") mgr.Enable("client")
// Templates. // Templates.
@ -104,12 +106,12 @@ func TestEnableDisable(t *testing.T) {
root := setupPluginDir(t, map[string]*fsDir{ root := setupPluginDir(t, map[string]*fsDir{
"plugin-a": { "plugin-a": {
files: map[string][]byte{ files: map[string][]byte{
"plugin.json": []byte(`{"name": "a", "version": "1.0"}`), "plugin.json": []byte(`{"name": "a", "version": "1.0", "hooks": {"on_install": "on_install"}}`),
}, },
}, },
"plugin-b": { "plugin-b": {
files: map[string][]byte{ files: map[string][]byte{
"plugin.json": []byte(`{"name": "b", "version": "1.0"}`), "plugin.json": []byte(`{"name": "b", "version": "1.0", "hooks": {"on_install": "on_install"}}`),
}, },
}, },
}) })
@ -126,7 +128,9 @@ func TestEnableDisable(t *testing.T) {
t.Errorf("active after discover = %d, want 0", len(mgr.Active())) t.Errorf("active after discover = %d, want 0", len(mgr.Active()))
} }
// Enable both. // Install & enable both.
mgr.Install("a")
mgr.Install("b")
mgr.Enable("a") mgr.Enable("a")
mgr.Enable("b") mgr.Enable("b")
if len(mgr.Active()) != 2 { if len(mgr.Active()) != 2 {
@ -148,12 +152,14 @@ func TestEnableDisable(t *testing.T) {
func TestActiveNames(t *testing.T) { func TestActiveNames(t *testing.T) {
root := setupPluginDir(t, map[string]*fsDir{ root := setupPluginDir(t, map[string]*fsDir{
"p1": {files: map[string][]byte{"plugin.json": []byte(`{"name":"p1"}`)}}, "p1": {files: map[string][]byte{"plugin.json": []byte(`{"name":"p1","hooks":{"on_install":"on_install"}}`)}},
"p2": {files: map[string][]byte{"plugin.json": []byte(`{"name":"p2"}`)}}, "p2": {files: map[string][]byte{"plugin.json": []byte(`{"name":"p2","hooks":{"on_install":"on_install"}}`)}},
}) })
mgr := NewManager(root) mgr := NewManager(root)
mgr.Discover() mgr.Discover()
mgr.Install("p1")
mgr.Install("p2")
mgr.Enable("p1") mgr.Enable("p1")
mgr.Enable("p2") mgr.Enable("p2")
mgr.Disable("p1") mgr.Disable("p1")

View File

@ -215,7 +215,7 @@ func TestPluginManager_InitRuntimes(t *testing.T) {
pj := `{ pj := `{
"name": "testp", "name": "testp",
"version": "1.0.0", "version": "1.0.0",
"hooks": { "on_init": "on_init" } "hooks": { "on_install": "on_install", "on_init": "on_init" }
}` }`
if err := os.WriteFile(filepath.Join(pluginsDir, "plugin.json"), []byte(pj), 0644); err != nil { if err := os.WriteFile(filepath.Join(pluginsDir, "plugin.json"), []byte(pj), 0644); err != nil {
t.Fatal(err) t.Fatal(err)
@ -228,6 +228,7 @@ func TestPluginManager_InitRuntimes(t *testing.T) {
mgr := NewManager(dir) mgr := NewManager(dir)
mgr.Discover() mgr.Discover()
mgr.Install("testp")
mgr.Enable("testp") mgr.Enable("testp")
mgr.InitRuntimes() mgr.InitRuntimes()
defer mgr.CloseRuntimes() defer mgr.CloseRuntimes()