feat(04-01): scaffold Winter-shaped plugins with generated registry
Render plugin.go, routes.go, leaf packages and go.mod from embedded templates, emit empty registry accessors, and define job, admin, lang and mail capability contracts so a new plugin compiles before artifacts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
95
internal/build/registry.go
Normal file
95
internal/build/registry.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const registryFileName = "registry.gen.go"
|
||||
|
||||
type registryRef struct {
|
||||
Package string
|
||||
Ident string
|
||||
}
|
||||
|
||||
type registryData struct {
|
||||
Package string
|
||||
Module string
|
||||
Imports []string
|
||||
Models []registryRef
|
||||
Migrations []registryRef
|
||||
Commands []registryRef
|
||||
Jobs []registryRef
|
||||
AdminControllers []registryRef
|
||||
}
|
||||
|
||||
func emptyRegistry(pkg, module string) registryData {
|
||||
data := registryData{Package: pkg, Module: module}
|
||||
data.prepare()
|
||||
return data
|
||||
}
|
||||
|
||||
func (d *registryData) prepare() {
|
||||
d.Imports = uniqueLeafImports(d.Module, d.Models, d.Migrations, d.Commands, d.Jobs, d.AdminControllers)
|
||||
}
|
||||
|
||||
func uniqueLeafImports(module string, groups ...[]registryRef) []string {
|
||||
if module == "" {
|
||||
return nil
|
||||
}
|
||||
seen := make(map[string]struct{})
|
||||
var imports []string
|
||||
for _, group := range groups {
|
||||
for _, ref := range group {
|
||||
if ref.Package == "" {
|
||||
continue
|
||||
}
|
||||
path := module + "/" + ref.Package
|
||||
if _, ok := seen[path]; ok {
|
||||
continue
|
||||
}
|
||||
seen[path] = struct{}{}
|
||||
imports = append(imports, path)
|
||||
}
|
||||
}
|
||||
sort.Strings(imports)
|
||||
return imports
|
||||
}
|
||||
|
||||
func writeRegistry(pluginDir string, data registryData) error {
|
||||
data.prepare()
|
||||
src, err := renderStub("registry.go", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(pluginDir, registryFileName)
|
||||
existing, err := os.ReadFile(path)
|
||||
if err == nil && bytes.Equal(existing, src) {
|
||||
return nil
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("build: read %s: %w", path, err)
|
||||
}
|
||||
tmp, err := os.CreateTemp(pluginDir, registryFileName+".*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("build: create %s temp: %w", registryFileName, err)
|
||||
}
|
||||
tmpName := tmp.Name()
|
||||
if _, err := tmp.Write(src); err != nil {
|
||||
tmp.Close()
|
||||
os.Remove(tmpName)
|
||||
return fmt.Errorf("build: write %s temp: %w", registryFileName, err)
|
||||
}
|
||||
if err := tmp.Close(); err != nil {
|
||||
os.Remove(tmpName)
|
||||
return fmt.Errorf("build: close %s temp: %w", registryFileName, err)
|
||||
}
|
||||
if err := os.Rename(tmpName, path); err != nil {
|
||||
os.Remove(tmpName)
|
||||
return fmt.Errorf("build: rename %s: %w", registryFileName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user