a4b5651e8c
Refactoriza la ejecucion de funciones Python en fn run. Extrae la logica a pyrunner.go con soporte para importar dependencias del registry y ejecutar con el venv del proyecto. Agrega WalCheckpoint en db.go para que lectores externos vean datos actualizados tras fn index.
169 lines
4.6 KiB
Go
169 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"fn-registry/registry"
|
|
)
|
|
|
|
func cmdRun(args []string) {
|
|
if len(args) == 0 {
|
|
fmt.Fprintln(os.Stderr, "usage: fn run <id_or_name> [args...]")
|
|
os.Exit(1)
|
|
}
|
|
|
|
idOrName := args[0]
|
|
passArgs := args[1:]
|
|
|
|
registryRoot := root()
|
|
dbPath := filepath.Join(registryRoot, dbName)
|
|
|
|
db, err := registry.Open(dbPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: cannot open registry: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
defer db.Close()
|
|
|
|
fn, err := resolveFunction(db, idOrName)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if fn.FilePath == "" {
|
|
fmt.Fprintf(os.Stderr, "error: %s has no file_path in registry\n", fn.ID)
|
|
os.Exit(1)
|
|
}
|
|
|
|
absPath := filepath.Join(registryRoot, fn.FilePath)
|
|
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
|
fmt.Fprintf(os.Stderr, "error: file not found: %s\n", absPath)
|
|
os.Exit(1)
|
|
}
|
|
|
|
cmd, err := buildCommand(fn, db, registryRoot, absPath, passArgs)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdin = os.Stdin
|
|
|
|
fmt.Fprintf(os.Stderr, "[fn run] %s (%s/%s) %s\n", fn.ID, fn.Lang, fn.Kind, strings.Join(passArgs, " "))
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
os.Exit(exitErr.ExitCode())
|
|
}
|
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func resolveFunction(db *registry.DB, idOrName string) (*registry.Function, error) {
|
|
fn, err := db.GetFunction(idOrName)
|
|
if err == nil {
|
|
return fn, nil
|
|
}
|
|
|
|
fns, err := db.GetFunctionsByName(idOrName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lookup failed: %w", err)
|
|
}
|
|
if len(fns) == 0 {
|
|
return nil, fmt.Errorf("function %q not found (tried as ID and name)", idOrName)
|
|
}
|
|
if len(fns) == 1 {
|
|
return &fns[0], nil
|
|
}
|
|
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "ambiguous name %q — found %d matches:\n", idOrName, len(fns))
|
|
for _, f := range fns {
|
|
fmt.Fprintf(&b, " %s (%s/%s)\n", f.ID, f.Lang, f.Kind)
|
|
}
|
|
fmt.Fprintf(&b, "use the full ID to disambiguate")
|
|
return nil, fmt.Errorf("%s", b.String())
|
|
}
|
|
|
|
func buildCommand(fn *registry.Function, db *registry.DB, registryRoot, absPath string, args []string) (*exec.Cmd, error) {
|
|
switch fn.Lang {
|
|
case "go":
|
|
return buildGoCommand(fn, registryRoot, absPath, args)
|
|
case "py":
|
|
return buildPyRunnerCommand(fn, db, registryRoot, args)
|
|
case "bash":
|
|
return buildBashCommand(absPath, args)
|
|
case "ts":
|
|
return buildTsCommand(registryRoot, absPath, args)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported lang %q for execution", fn.Lang)
|
|
}
|
|
}
|
|
|
|
func buildGoCommand(fn *registry.Function, registryRoot, absPath string, args []string) (*exec.Cmd, error) {
|
|
dir := filepath.Dir(absPath)
|
|
env := append(os.Environ(), "CGO_ENABLED=1")
|
|
|
|
// If directory has main.go → go run . (pipelines and standalone executables)
|
|
mainGo := filepath.Join(dir, "main.go")
|
|
if _, err := os.Stat(mainGo); err == nil {
|
|
cmdArgs := append([]string{"run", "."}, args...)
|
|
cmd := exec.Command("go", cmdArgs...)
|
|
cmd.Dir = dir
|
|
cmd.Env = env
|
|
return cmd, nil
|
|
}
|
|
|
|
// Library code: if it has tests → go test
|
|
if fn.Tested && fn.TestFilePath != "" {
|
|
testAbs := filepath.Join(registryRoot, fn.TestFilePath)
|
|
if _, err := os.Stat(testAbs); err == nil {
|
|
relPkg, _ := filepath.Rel(registryRoot, dir)
|
|
pkgPath := "./" + filepath.ToSlash(relPkg)
|
|
cmdArgs := append([]string{"test", "-v", "-count=1", "-tags", "fts5", pkgPath}, args...)
|
|
cmd := exec.Command("go", cmdArgs...)
|
|
cmd.Dir = registryRoot
|
|
cmd.Env = env
|
|
return cmd, nil
|
|
}
|
|
}
|
|
|
|
// No tests: go vet (compilation check)
|
|
relPkg, _ := filepath.Rel(registryRoot, dir)
|
|
pkgPath := "./" + filepath.ToSlash(relPkg)
|
|
cmdArgs := []string{"vet", "-tags", "fts5", pkgPath}
|
|
cmd := exec.Command("go", cmdArgs...)
|
|
cmd.Dir = registryRoot
|
|
cmd.Env = env
|
|
fmt.Fprintf(os.Stderr, "[fn run] %s is library code without tests — running go vet\n", fn.ID)
|
|
return cmd, nil
|
|
}
|
|
|
|
|
|
func buildBashCommand(absPath string, args []string) (*exec.Cmd, error) {
|
|
cmdArgs := append([]string{absPath}, args...)
|
|
cmd := exec.Command("bash", cmdArgs...)
|
|
cmd.Dir = filepath.Dir(absPath)
|
|
return cmd, nil
|
|
}
|
|
|
|
func buildTsCommand(registryRoot, absPath string, args []string) (*exec.Cmd, error) {
|
|
tsxBin := filepath.Join(registryRoot, "frontend", "node_modules", ".bin", "tsx")
|
|
if _, err := os.Stat(tsxBin); os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("tsx not found — run: cd frontend && pnpm add -D tsx")
|
|
}
|
|
|
|
cmdArgs := append([]string{absPath}, args...)
|
|
cmd := exec.Command(tsxBin, cmdArgs...)
|
|
cmd.Dir = filepath.Dir(absPath)
|
|
return cmd, nil
|
|
}
|