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 class="plugin-toggle">
{#if p.hasInstall && !p.installed}
{#if !p.installed}
<button class="install-btn" on:click={() => install(p)}>
📦 Установить
</button>
{:else if p.hasInstall && p.installed}
{:else}
<div class="toggle-group">
<button
class="toggle-btn"
@ -126,16 +126,6 @@
🗑
</button>
</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}
</div>
</div>

View File

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

View File

@ -41,7 +41,8 @@ func TestDiscover(t *testing.T) {
"plugin.json": []byte(`{
"name": "client",
"version": "1.0.0",
"description": "Client template",
"description": "Client plugin",
"hooks": { "on_install": "on_install" },
"templates": ["client"]
}`),
},
@ -78,7 +79,8 @@ func TestDiscover(t *testing.T) {
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")
// Templates.
@ -104,12 +106,12 @@ func TestEnableDisable(t *testing.T) {
root := setupPluginDir(t, map[string]*fsDir{
"plugin-a": {
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": {
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()))
}
// Enable both.
// Install & enable both.
mgr.Install("a")
mgr.Install("b")
mgr.Enable("a")
mgr.Enable("b")
if len(mgr.Active()) != 2 {
@ -148,12 +152,14 @@ func TestEnableDisable(t *testing.T) {
func TestActiveNames(t *testing.T) {
root := setupPluginDir(t, map[string]*fsDir{
"p1": {files: map[string][]byte{"plugin.json": []byte(`{"name":"p1"}`)}},
"p2": {files: map[string][]byte{"plugin.json": []byte(`{"name":"p2"}`)}},
"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","hooks":{"on_install":"on_install"}}`)}},
})
mgr := NewManager(root)
mgr.Discover()
mgr.Install("p1")
mgr.Install("p2")
mgr.Enable("p1")
mgr.Enable("p2")
mgr.Disable("p1")

View File

@ -215,7 +215,7 @@ func TestPluginManager_InitRuntimes(t *testing.T) {
pj := `{
"name": "testp",
"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 {
t.Fatal(err)
@ -228,6 +228,7 @@ func TestPluginManager_InitRuntimes(t *testing.T) {
mgr := NewManager(dir)
mgr.Discover()
mgr.Install("testp")
mgr.Enable("testp")
mgr.InitRuntimes()
defer mgr.CloseRuntimes()