feat(kiosk): resolve constant plugin IDs, overrides and the CheckT helper
- kiosk.ReadIdentity: ID()/Requires() as literals or constants across packages and modules, read with go/parser, never run - plugin minimums from go.mod requires; v0.0.0 is an error, a pseudo-version warns - kiosk.Validate, kiosk.CheckT (no testing import) - plugin:add reads IDs through kiosk.ReadIdentity, so const-ID plugins register
This commit is contained in:
@@ -1322,6 +1322,39 @@ func assertWinterPluginShape(t *testing.T, pluginDir string) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddPluginConstID: plugin:add registers a plugin whose ID() returns a
|
||||
// package-level constant (research F1), and a literal-ID plugin as before.
|
||||
func TestAddPluginConstID(t *testing.T) {
|
||||
dir := copyHelloApp(t)
|
||||
ctx := t.Context()
|
||||
plugins := map[string]string{
|
||||
"consts": "package consts\n\nconst PluginID = \"acme.consts\"\n\ntype Plugin struct{}\n\nfunc (*Plugin) ID() string { return PluginID }\n\nfunc (*Plugin) Requires() []string { return nil }\n",
|
||||
"lit": "package lit\n\ntype Plugin struct{}\n\nfunc (*Plugin) ID() string { return \"acme.lit\" }\n\nfunc (*Plugin) Requires() []string { return nil }\n",
|
||||
}
|
||||
for _, name := range []string{"consts", "lit"} {
|
||||
pluginDir := filepath.Join(dir, "plugins", "acme", name)
|
||||
if err := os.MkdirAll(pluginDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
write(t, filepath.Join(pluginDir, "go.mod"), "module example.test/acme/"+name+"\n\ngo 1.27.0\n")
|
||||
write(t, filepath.Join(pluginDir, "plugin.go"), plugins[name])
|
||||
if err := AddPlugin(ctx, dir, filepath.Join("plugins", "acme", name)); err != nil {
|
||||
t.Fatalf("plugin:add %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
m, err := LoadManifest(filepath.Join(dir, "summer.yaml"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := map[string]string{}
|
||||
for _, p := range m.Plugins {
|
||||
got[p.ID] = p.Module
|
||||
}
|
||||
if got["acme.consts"] != "example.test/acme/consts" || got["acme.lit"] != "example.test/acme/lit" {
|
||||
t.Fatalf("summer.yaml plugins = %+v", m.Plugins)
|
||||
}
|
||||
}
|
||||
|
||||
func copyHelloApp(t *testing.T) string {
|
||||
t.Helper()
|
||||
src, err := filepath.Abs(filepath.Join("..", "..", "examples", "hello"))
|
||||
|
||||
@@ -5,19 +5,16 @@ import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/version"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/bonfire"
|
||||
"git.golem15.com/golem15/summercms/modules/kiosk"
|
||||
)
|
||||
|
||||
//go:embed stubs/*.tmpl
|
||||
@@ -357,38 +354,19 @@ func frameworkReplaceFor(pluginDir, appDir string) (string, error) {
|
||||
return filepath.ToSlash(rel), nil
|
||||
}
|
||||
|
||||
// pluginIDFromGo reads the plugin ID from the package of the plugin.go at
|
||||
// path with kiosk.ReadIdentity: a string literal or a constant, resolved
|
||||
// without running any code.
|
||||
func pluginIDFromGo(path string) (string, error) {
|
||||
fset := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fset, path, nil, 0)
|
||||
if err != nil {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return "", fmt.Errorf("build: parse %s: %w", path, err)
|
||||
}
|
||||
var id string
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
fn, ok := n.(*ast.FuncDecl)
|
||||
if !ok || fn.Name == nil || fn.Name.Name != "ID" || fn.Recv == nil || fn.Body == nil {
|
||||
return true
|
||||
}
|
||||
for _, stmt := range fn.Body.List {
|
||||
ret, ok := stmt.(*ast.ReturnStmt)
|
||||
if !ok || len(ret.Results) != 1 {
|
||||
continue
|
||||
}
|
||||
lit, ok := ret.Results[0].(*ast.BasicLit)
|
||||
if !ok || lit.Kind != token.STRING {
|
||||
continue
|
||||
}
|
||||
s, err := strconv.Unquote(lit.Value)
|
||||
if err == nil {
|
||||
id = s
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if id == "" {
|
||||
return "", fmt.Errorf("build: %s does not return a plugin id", path)
|
||||
id, err := kiosk.ReadIdentity(context.Background(), filepath.Dir(path))
|
||||
if id.ID != "" {
|
||||
// A Requires() that kiosk cannot read does not hide the ID.
|
||||
return id.ID, nil
|
||||
}
|
||||
return id, nil
|
||||
return "", fmt.Errorf("build: %s does not return a plugin id: %w", path, err)
|
||||
}
|
||||
|
||||
func editAppModule(ctx context.Context, appDir, pluginDir, modPath string) error {
|
||||
|
||||
Reference in New Issue
Block a user