e02a950ee0
Funciones Go para crear apps Wails: scaffold estructura, bind CRUD genérico, build multiplataforma, emit eventos y stream de datos al frontend. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
216 lines
5.2 KiB
Go
216 lines
5.2 KiB
Go
package infra
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
// ScaffoldWailsAppConfig configura la generación del proyecto Wails.
|
|
type ScaffoldWailsAppConfig struct {
|
|
Name string // Nombre del proyecto
|
|
Dir string // Directorio destino
|
|
Title string // Título de la ventana
|
|
Width int // Ancho de la ventana (default 1024)
|
|
Height int // Alto de la ventana (default 768)
|
|
Author string // Nombre del autor
|
|
FrontendLib string // Path a la frontend library (default ~/.local_agentes/frontend/frontend)
|
|
}
|
|
|
|
const mainGoTpl = `package main
|
|
|
|
import (
|
|
"embed"
|
|
|
|
"github.com/wailsapp/wails/v2"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
|
)
|
|
|
|
//go:embed all:frontend/dist
|
|
var assets embed.FS
|
|
|
|
func main() {
|
|
app := NewApp()
|
|
|
|
err := wails.Run(&options.App{
|
|
Title: "{{.Title}}",
|
|
Width: {{.Width}},
|
|
Height: {{.Height}},
|
|
AssetServer: &assetserver.Options{
|
|
Assets: assets,
|
|
},
|
|
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
|
OnStartup: app.startup,
|
|
Bind: []interface{}{
|
|
app,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
println("Error:", err.Error())
|
|
}
|
|
}
|
|
`
|
|
|
|
const appGoTpl = `package main
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
)
|
|
|
|
// App struct — cada método público se expone como binding IPC al frontend.
|
|
type App struct {
|
|
ctx context.Context
|
|
}
|
|
|
|
func NewApp() *App {
|
|
return &App{}
|
|
}
|
|
|
|
func (a *App) startup(ctx context.Context) {
|
|
a.ctx = ctx
|
|
}
|
|
|
|
// EmitEvent emite un evento al frontend.
|
|
func (a *App) EmitEvent(eventName string, data interface{}) {
|
|
runtime.EventsEmit(a.ctx, eventName, data)
|
|
}
|
|
|
|
// Ping verifica que el IPC funciona.
|
|
func (a *App) Ping() string {
|
|
return "pong"
|
|
}
|
|
`
|
|
|
|
const wailsJSONTpl = `{
|
|
"$schema": "https://wails.io/schemas/config.v2.json",
|
|
"name": "{{.Name}}",
|
|
"outputfilename": "{{.Name}}",
|
|
"frontend:dir": "./frontend",
|
|
"frontend:install": "pnpm install",
|
|
"frontend:build": "pnpm run build",
|
|
"frontend:dev:watcher": "pnpm run dev",
|
|
"frontend:dev:serverUrl": "auto",
|
|
"wailsjsdir": "./frontend/src/wailsjs",
|
|
"author": {
|
|
"name": "{{.Author}}"
|
|
}
|
|
}
|
|
`
|
|
|
|
const goModTpl = `module {{.Name}}
|
|
|
|
go 1.23
|
|
|
|
require github.com/wailsapp/wails/v2 v2.11.0
|
|
`
|
|
|
|
// ScaffoldWailsApp genera la estructura base de un proyecto Wails con frontend vinculado.
|
|
func ScaffoldWailsApp(ctx context.Context, cfg ScaffoldWailsAppConfig) error {
|
|
if cfg.Name == "" {
|
|
return fmt.Errorf("name is required")
|
|
}
|
|
if cfg.Dir == "" {
|
|
cfg.Dir = cfg.Name
|
|
}
|
|
if cfg.Title == "" {
|
|
cfg.Title = cfg.Name
|
|
}
|
|
if cfg.Width == 0 {
|
|
cfg.Width = 1024
|
|
}
|
|
if cfg.Height == 0 {
|
|
cfg.Height = 768
|
|
}
|
|
if cfg.Author == "" {
|
|
cfg.Author = "Agent"
|
|
}
|
|
if cfg.FrontendLib == "" {
|
|
home, _ := os.UserHomeDir()
|
|
cfg.FrontendLib = filepath.Join(home, ".local_agentes", "frontend", "frontend")
|
|
}
|
|
|
|
// Crear directorio
|
|
if err := os.MkdirAll(cfg.Dir, 0755); err != nil {
|
|
return fmt.Errorf("creating dir: %w", err)
|
|
}
|
|
|
|
// Generar archivos Go
|
|
files := map[string]string{
|
|
"main.go": mainGoTpl,
|
|
"app.go": appGoTpl,
|
|
"wails.json": wailsJSONTpl,
|
|
"go.mod": goModTpl,
|
|
}
|
|
|
|
for name, tpl := range files {
|
|
path := filepath.Join(cfg.Dir, name)
|
|
t, err := template.New(name).Parse(tpl)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing template %s: %w", name, err)
|
|
}
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
return fmt.Errorf("creating %s: %w", name, err)
|
|
}
|
|
if err := t.Execute(f, cfg); err != nil {
|
|
f.Close()
|
|
return fmt.Errorf("executing template %s: %w", name, err)
|
|
}
|
|
f.Close()
|
|
}
|
|
|
|
// Crear frontend como link simbólico o copiar template
|
|
frontendDir := filepath.Join(cfg.Dir, "frontend")
|
|
createProjectScript := filepath.Join(filepath.Dir(cfg.FrontendLib), "..", "scripts", "create-project.sh")
|
|
|
|
if _, err := os.Stat(createProjectScript); err == nil {
|
|
// Usar el script de Frontend_Library
|
|
cmd := exec.CommandContext(ctx, "bash", createProjectScript, cfg.Name, frontendDir, "--wails")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("creating frontend: %w", err)
|
|
}
|
|
} else {
|
|
// Fallback: crear frontend mínimo
|
|
if err := os.MkdirAll(filepath.Join(frontendDir, "src"), 0755); err != nil {
|
|
return fmt.Errorf("creating frontend dir: %w", err)
|
|
}
|
|
pkgJSON := fmt.Sprintf(`{"name":"%s-frontend","private":true,"scripts":{"dev":"vite","build":"vite build"}}`, cfg.Name)
|
|
os.WriteFile(filepath.Join(frontendDir, "package.json"), []byte(pkgJSON), 0644)
|
|
}
|
|
|
|
// go mod tidy
|
|
cmd := exec.CommandContext(ctx, "go", "mod", "tidy")
|
|
cmd.Dir = cfg.Dir
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
_ = cmd.Run() // No fatal si falla
|
|
|
|
fmt.Printf("Wails app scaffolded at %s\n", cfg.Dir)
|
|
return nil
|
|
}
|
|
|
|
// GenerateAppBinding genera el código Go para un método de binding.
|
|
func GenerateAppBinding(name string, params []string, returnType string, body string) string {
|
|
var sb strings.Builder
|
|
sb.WriteString(fmt.Sprintf("func (a *App) %s(", name))
|
|
sb.WriteString(strings.Join(params, ", "))
|
|
sb.WriteString(")")
|
|
if returnType != "" {
|
|
sb.WriteString(" " + returnType)
|
|
}
|
|
sb.WriteString(" {\n")
|
|
sb.WriteString("\t" + body + "\n")
|
|
sb.WriteString("}\n")
|
|
return sb.String()
|
|
}
|