From 181a37698410b68e00a885593b6f2b7acf21f4b4 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sun, 8 Mar 2026 20:40:21 +0000 Subject: api: generic ScriptRegistry; collapse script endpoints Replace hardcoded handleStartNextTask/handleDeploy with a single handleScript handler keyed by name from a ScriptRegistry map. Scripts are now configured via Server.SetScripts() rather than individual setter fields. Co-Authored-By: Claude Sonnet 4.6 --- internal/api/scripts.go | 64 ++++++++++++++----------------------------------- 1 file changed, 18 insertions(+), 46 deletions(-) (limited to 'internal/api/scripts.go') diff --git a/internal/api/scripts.go b/internal/api/scripts.go index 9afbb75..822bd32 100644 --- a/internal/api/scripts.go +++ b/internal/api/scripts.go @@ -5,62 +5,33 @@ import ( "context" "net/http" "os/exec" - "path/filepath" "time" ) const scriptTimeout = 30 * time.Second -func (s *Server) startNextTaskScriptPath() string { - if s.startNextTaskScript != "" { - return s.startNextTaskScript - } - return filepath.Join(s.workDir, "scripts", "start-next-task") -} +// ScriptRegistry maps endpoint names to executable script paths. +// Only registered scripts are exposed via POST /api/scripts/{name}. +type ScriptRegistry map[string]string -func (s *Server) deployScriptPath() string { - if s.deployScript != "" { - return s.deployScript - } - return filepath.Join(s.workDir, "scripts", "deploy") +// SetScripts configures the script registry. The mux is not re-registered; +// the handler looks up the registry at request time, so this may be called +// after NewServer but before the first request. +func (s *Server) SetScripts(r ScriptRegistry) { + s.scripts = r } -func (s *Server) handleStartNextTask(w http.ResponseWriter, r *http.Request) { - ctx, cancel := context.WithTimeout(r.Context(), scriptTimeout) - defer cancel() - - scriptPath := s.startNextTaskScriptPath() - cmd := exec.CommandContext(ctx, scriptPath) - - var stdout, stderr bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = &stderr - - err := cmd.Run() - exitCode := 0 - if err != nil { - if exitErr, ok := err.(*exec.ExitError); ok { - exitCode = exitErr.ExitCode() - } else { - s.logger.Error("start-next-task: script execution failed", "error", err, "path", scriptPath) - writeJSON(w, http.StatusInternalServerError, map[string]string{ - "error": "script execution failed: " + err.Error(), - }) - return - } +func (s *Server) handleScript(w http.ResponseWriter, r *http.Request) { + name := r.PathValue("name") + scriptPath, ok := s.scripts[name] + if !ok { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "script not found: " + name}) + return } - writeJSON(w, http.StatusOK, map[string]interface{}{ - "output": stdout.String(), - "exit_code": exitCode, - }) -} - -func (s *Server) handleDeploy(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), scriptTimeout) defer cancel() - scriptPath := s.deployScriptPath() cmd := exec.CommandContext(ctx, scriptPath) var stdout, stderr bytes.Buffer @@ -72,17 +43,18 @@ func (s *Server) handleDeploy(w http.ResponseWriter, r *http.Request) { if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { exitCode = exitErr.ExitCode() + s.logger.Warn("script exited non-zero", "name", name, "exit_code", exitCode, "stderr", stderr.String()) } else { - s.logger.Error("deploy: script execution failed", "error", err, "path", scriptPath) + s.logger.Error("script execution failed", "name", name, "error", err, "path", scriptPath) writeJSON(w, http.StatusInternalServerError, map[string]string{ - "error": "script execution failed: " + err.Error(), + "error": "script execution failed", }) return } } writeJSON(w, http.StatusOK, map[string]interface{}{ - "output": stdout.String() + stderr.String(), + "output": stdout.String(), "exit_code": exitCode, }) } -- cgit v1.2.3