aea21d713c
Parsea flags de -help de cada pipeline para mostrar formulario de argumentos antes de ejecutar. Filtra pipelines por tag 'launcher'. Corrige selección en historial delegando enter al list antes de leer item.
147 lines
3.4 KiB
Go
147 lines
3.4 KiB
Go
package views
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
ops "fn-registry/fn_operations"
|
|
"fn-registry/registry"
|
|
)
|
|
|
|
// PipelineFlag describes a CLI flag parsed from -help output.
|
|
type PipelineFlag struct {
|
|
Name string // e.g. "project"
|
|
Type string // e.g. "string"
|
|
Desc string // description text
|
|
Default string // default value, empty if none
|
|
Required bool // true if no default
|
|
}
|
|
|
|
var flagLineRe = regexp.MustCompile(`^\s+-(\S+)\s+(\S+)$`)
|
|
var defaultRe = regexp.MustCompile(`\(default "(.*)"\)`)
|
|
|
|
// GetPipelineFlags runs `go run . -help` and parses the flag output.
|
|
func GetPipelineFlags(fn *registry.Function, registryRoot string) []PipelineFlag {
|
|
absPath := filepath.Join(registryRoot, fn.FilePath)
|
|
dir := filepath.Dir(absPath)
|
|
|
|
cmd := exec.Command("go", "run", ".", "-help")
|
|
cmd.Dir = dir
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
cmd.Run() // -help exits with code 2, ignore error
|
|
|
|
return parseFlags(stderr.String())
|
|
}
|
|
|
|
func parseFlags(output string) []PipelineFlag {
|
|
var flags []PipelineFlag
|
|
lines := strings.Split(output, "\n")
|
|
|
|
for i := 0; i < len(lines); i++ {
|
|
m := flagLineRe.FindStringSubmatch(lines[i])
|
|
if m == nil {
|
|
continue
|
|
}
|
|
f := PipelineFlag{Name: m[1], Type: m[2]}
|
|
|
|
// Next line is the description
|
|
if i+1 < len(lines) {
|
|
desc := strings.TrimSpace(lines[i+1])
|
|
if dm := defaultRe.FindStringSubmatch(desc); dm != nil {
|
|
f.Default = dm[1]
|
|
f.Desc = strings.TrimSpace(defaultRe.ReplaceAllString(desc, ""))
|
|
} else {
|
|
f.Desc = desc
|
|
}
|
|
i++
|
|
}
|
|
|
|
f.Required = f.Default == "" && !strings.Contains(strings.ToLower(f.Desc), "opcional")
|
|
flags = append(flags, f)
|
|
}
|
|
return flags
|
|
}
|
|
|
|
// RunResult holds the outcome of a pipeline execution.
|
|
type RunResult struct {
|
|
Stdout string
|
|
Stderr string
|
|
ExecID string
|
|
PipelineID string
|
|
Status ops.ExecutionStatus
|
|
DurationMs int64
|
|
Err error
|
|
}
|
|
|
|
// RunPipeline executes a pipeline as a subprocess and records the execution.
|
|
func RunPipeline(fn *registry.Function, registryRoot string, opsDB *ops.DB, args []string) RunResult {
|
|
absPath := filepath.Join(registryRoot, fn.FilePath)
|
|
dir := filepath.Dir(absPath)
|
|
|
|
startedAt := time.Now().UTC()
|
|
|
|
cmdArgs := append([]string{"run", "."}, args...)
|
|
cmd := exec.Command("go", cmdArgs...)
|
|
cmd.Dir = dir
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
err := cmd.Run()
|
|
endedAt := time.Now().UTC()
|
|
|
|
status := ops.ExecSuccess
|
|
var execErr string
|
|
if err != nil {
|
|
status = ops.ExecFailure
|
|
execErr = err.Error()
|
|
if stderr.Len() > 0 {
|
|
execErr = stderr.String()
|
|
}
|
|
}
|
|
|
|
execID := fmt.Sprintf("exec_%d", time.Now().UnixNano())
|
|
durationMs := endedAt.Sub(startedAt).Milliseconds()
|
|
|
|
execution := &ops.Execution{
|
|
ID: execID,
|
|
PipelineID: fn.ID,
|
|
Status: status,
|
|
StartedAt: startedAt,
|
|
EndedAt: &endedAt,
|
|
DurationMs: &durationMs,
|
|
Error: execErr,
|
|
CreatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
insertErr := ops.InsertExecutionSafe(opsDB, execution)
|
|
if insertErr != nil {
|
|
return RunResult{
|
|
Stdout: stdout.String(),
|
|
Stderr: stderr.String(),
|
|
ExecID: execID,
|
|
PipelineID: fn.ID,
|
|
Status: status,
|
|
DurationMs: durationMs,
|
|
Err: fmt.Errorf("pipeline ran but failed to record: %w", insertErr),
|
|
}
|
|
}
|
|
|
|
return RunResult{
|
|
Stdout: stdout.String(),
|
|
Stderr: stderr.String(),
|
|
ExecID: execID,
|
|
PipelineID: fn.ID,
|
|
Status: status,
|
|
DurationMs: durationMs,
|
|
Err: err,
|
|
}
|
|
}
|