Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ clean: ## Remove binary

dump-state:
./contracts/anvil/dump-state.sh

build/test-plugin:
@go build -buildmode=plugin -o ./bin/test-plugin.so cmd/test-plugin/main.go
13 changes: 13 additions & 0 deletions cmd/devkit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"context"
"devkit-cli/pkg/plugin"
"fmt"
"log"
"os"

Expand All @@ -24,6 +26,17 @@ func main() {
UseShortOptionHandling: true,
}

plugins, err := plugin.LoadPlugins("~/.devkit/plugins")
if err != nil {
log.Fatalf("Error loading plugins: %v", err)
}
fmt.Printf("Plugins loaded: %+v\n", plugins)
for _, p := range plugins {
if p != nil {
app.Commands = append(app.Commands, p.GetCommands()...)
}
}

// Apply both middleware functions to all commands
hooks.ApplyMiddleware(app.Commands, hooks.WithEnvLoader, hooks.WithTelemetry)

Expand Down
34 changes: 34 additions & 0 deletions cmd/test-plugin/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"devkit-cli/pkg/plugin"
"github.com/urfave/cli/v2"
)

type TestPlugin struct {
}

func (p *TestPlugin) Version() string {
return "v1.0.0"
}

func (p *TestPlugin) Name() string {
return "TestPlugin"
}

func (p *TestPlugin) GetCommands() []*cli.Command {
return []*cli.Command{
{
Name: "test",
Usage: "Test command from TestPlugin",
Action: func(c *cli.Context) error {
println("Test command executed")
return nil
},
},
}
}

func GetPlugin() plugin.IPlugin {
return &TestPlugin{}
}
8 changes: 8 additions & 0 deletions pkg/commands/avs.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,11 @@
ReleaseCommand,
},
}

func MergeCommands(cmds ...*cli.Command) []*cli.Command {
merged := make([]*cli.Command, 0)
for _, cmd := range cmds {

Check failure on line 22 in pkg/commands/avs.go

View workflow job for this annotation

GitHub Actions / Lint

S1011: should replace loop with `merged = append(merged, cmds...)` (gosimple)

Check failure on line 22 in pkg/commands/avs.go

View workflow job for this annotation

GitHub Actions / Lint

S1011: should replace loop with `merged = append(merged, cmds...)` (gosimple)
merged = append(merged, cmd)
}
return merged
}
87 changes: 87 additions & 0 deletions pkg/plugin/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package plugin

import (
"fmt"
"github.com/urfave/cli/v2"
"log"
"os"
"path/filepath"
goplugin "plugin"
"strings"
)

type IPlugin interface {
GetCommands() []*cli.Command
Name() string
Version() string
}

func resolvePluginPath(pluginPath string) (string, error) {
if pluginPath == "~" || strings.HasPrefix(pluginPath, "~/") {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %v", err)
}
if pluginPath == "~" {
pluginPath = homeDir
} else {

Check failure on line 27 in pkg/plugin/plugin.go

View workflow job for this annotation

GitHub Actions / Lint

SA9003: empty branch (staticcheck)

Check failure on line 27 in pkg/plugin/plugin.go

View workflow job for this annotation

GitHub Actions / Lint

SA9003: empty branch (staticcheck)

}
pluginPath = filepath.Join(homeDir, pluginPath[2:])
}
return filepath.Abs(pluginPath)
}

func LoadPlugins(pluginPath string) ([]IPlugin, error) {
absPluginPath, err := resolvePluginPath(pluginPath)
if err != nil {
return nil, fmt.Errorf("failed to resolve symlinks: %v", err)
}
fmt.Printf("Resolved path: %s\n", absPluginPath)

pathStat, err := os.Stat(absPluginPath)
if err != nil {
if os.IsNotExist(err) {
log.Printf("warning: plugin path does not exist: %s", absPluginPath)
return nil, nil
}
return nil, fmt.Errorf("failed to stat plugin path: %v", err)
}
if !pathStat.IsDir() {
return nil, fmt.Errorf("plugin path is not a directory: %s", absPluginPath)
}

var plugins []IPlugin
files, err := filepath.Glob(filepath.Join(absPluginPath, "*.so"))
if err != nil {
return nil, fmt.Errorf("failed to glob plugin files: %v", err)
}

if len(files) == 0 {
return nil, nil
}

for _, file := range files {
p, err := goplugin.Open(file)
if err != nil {
return nil, fmt.Errorf("failed to open plugin file %s: %v", file, err)
}

sym, err := p.Lookup("GetPlugin")
if err != nil {
log.Printf("failed to lookup GetPlugin in %s: %v", file, err)
continue
}

getPlugin, ok := sym.(func() IPlugin)
if !ok {
log.Printf("Plugin %s has invalid 'GetPlugin' symbol", file)
continue
}

plugin := getPlugin()
plugins = append(plugins, plugin)
log.Printf("Loaded plugin: %s, version: %s", plugin.Name(), plugin.Version())
}
return plugins, nil
}
Loading