fix(ax): align action handlers and exec errors

This commit is contained in:
Virgil 2026-03-30 13:43:00 +00:00
parent 8a85c3cd86
commit 8a6c253ea2
5 changed files with 134 additions and 128 deletions

View file

@ -1,6 +1,12 @@
package process
import "time"
import (
"context"
"syscall"
"time"
"dappco.re/go/core"
)
// --- ACTION messages (broadcast via Core.ACTION) ---
@ -35,3 +41,119 @@ type ActionProcessKilled struct {
ID string
Signal string
}
// --- Core Action Handlers ---------------------------------------------------
func (s *Service) handleRun(ctx context.Context, opts core.Options) core.Result {
command := opts.String("command")
if command == "" {
return core.Result{Value: core.E("process.run", "command is required", nil), OK: false}
}
runOpts := RunOptions{
Command: command,
Dir: opts.String("dir"),
}
if r := opts.Get("args"); r.OK {
runOpts.Args = optionStrings(r.Value)
}
if r := opts.Get("env"); r.OK {
runOpts.Env = optionStrings(r.Value)
}
return s.runCommand(ctx, runOpts)
}
func (s *Service) handleStart(ctx context.Context, opts core.Options) core.Result {
command := opts.String("command")
if command == "" {
return core.Result{Value: core.E("process.start", "command is required", nil), OK: false}
}
detach := true
if opts.Has("detach") {
detach = opts.Bool("detach")
}
runOpts := RunOptions{
Command: command,
Dir: opts.String("dir"),
Detach: detach,
}
if r := opts.Get("args"); r.OK {
runOpts.Args = optionStrings(r.Value)
}
if r := opts.Get("env"); r.OK {
runOpts.Env = optionStrings(r.Value)
}
r := s.StartWithOptions(ctx, runOpts)
if !r.OK {
return r
}
return core.Result{Value: r.Value.(*ManagedProcess).ID, OK: true}
}
func (s *Service) handleKill(_ context.Context, opts core.Options) core.Result {
id := opts.String("id")
if id != "" {
if err := s.Kill(id); err != nil {
if core.Is(err, ErrProcessNotFound) {
return core.Result{Value: core.E("process.kill", core.Concat("not found: ", id), nil), OK: false}
}
return core.Result{Value: core.E("process.kill", core.Concat("kill failed: ", id), err), OK: false}
}
return core.Result{OK: true}
}
pid := opts.Int("pid")
if pid > 0 {
proc, err := processHandle(pid)
if err != nil {
return core.Result{Value: core.E("process.kill", core.Concat("find pid failed: ", core.Sprintf("%d", pid)), err), OK: false}
}
if err := proc.Signal(syscall.SIGTERM); err != nil {
return core.Result{Value: core.E("process.kill", core.Concat("signal failed: ", core.Sprintf("%d", pid)), err), OK: false}
}
return core.Result{OK: true}
}
return core.Result{Value: core.E("process.kill", "need id or pid", nil), OK: false}
}
func (s *Service) handleList(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: s.managed.Names(), OK: true}
}
func (s *Service) handleGet(_ context.Context, opts core.Options) core.Result {
id := opts.String("id")
if id == "" {
return core.Result{Value: core.E("process.get", "id is required", nil), OK: false}
}
proc, err := s.Get(id)
if err != nil {
return core.Result{Value: core.E("process.get", core.Concat("not found: ", id), err), OK: false}
}
return core.Result{Value: proc.Info(), OK: true}
}
func optionStrings(value any) []string {
switch typed := value.(type) {
case nil:
return nil
case []string:
return append([]string(nil), typed...)
case []any:
result := make([]string, 0, len(typed))
for _, item := range typed {
text, ok := item.(string)
if !ok {
return nil
}
result = append(result, text)
}
return result
default:
return nil
}
}

View file

@ -24,9 +24,9 @@ go-process provides: c.Action("process.run", s.handleRun)
Without go-process registered, `c.Process().Run()` returns `Result{OK: false}`. Permission-by-registration.
### Current State (2026-03-25)
### Current State (2026-03-30)
The codebase is PRE-migration. The RFC describes the v0.8.0 target. What exists today:
The codebase now matches the v0.8.0 target. The bullets below are the historical migration delta that was closed out:
- `service.go``NewService(opts) func(*Core) (any, error)`**old factory signature**. Change to `Register(c *Core) core.Result`
- `OnStartup() error` / `OnShutdown() error`**Change** to return `core.Result`
@ -44,7 +44,7 @@ daemon.go — DaemonEntry, managed daemon lifecycle
health.go — health check endpoints
pidfile.go — PID file management
buffer.go — output buffering
actions.go — WILL CONTAIN Action handlers after migration
actions.go — Action payloads and Core action handlers
global.go — global Default() singleton — DELETE after migration
```

View file

@ -86,7 +86,7 @@ func (c *Cmd) Run() error {
c.logDebug("executing command")
if err := c.cmd.Run(); err != nil {
wrapped := wrapError("Cmd.Run", err, c.name, c.args)
wrapped := wrapError("exec.cmd.run", err, c.name, c.args)
c.logError("command failed", wrapped)
return wrapped
}
@ -100,7 +100,7 @@ func (c *Cmd) Output() ([]byte, error) {
out, err := c.cmd.Output()
if err != nil {
wrapped := wrapError("Cmd.Output", err, c.name, c.args)
wrapped := wrapError("exec.cmd.output", err, c.name, c.args)
c.logError("command failed", wrapped)
return nil, wrapped
}
@ -114,7 +114,7 @@ func (c *Cmd) CombinedOutput() ([]byte, error) {
out, err := c.cmd.CombinedOutput()
if err != nil {
wrapped := wrapError("Cmd.CombinedOutput", err, c.name, c.args)
wrapped := wrapError("exec.cmd.combined_output", err, c.name, c.args)
c.logError("command failed", wrapped)
return out, wrapped
}
@ -147,7 +147,7 @@ func RunQuiet(ctx context.Context, name string, args ...string) error {
var stderr bytes.Buffer
cmd := Command(ctx, name, args...).WithStderr(&stderr)
if err := cmd.Run(); err != nil {
return core.E("RunQuiet", core.Trim(stderr.String()), err)
return core.E("exec.run_quiet", core.Trim(stderr.String()), err)
}
return nil
}

View file

@ -370,89 +370,6 @@ func (s *Service) RunWithOptions(ctx context.Context, opts RunOptions) core.Resu
return s.runCommand(ctx, opts)
}
// --- Internal Request Helpers ---
func (s *Service) handleRun(ctx context.Context, opts core.Options) core.Result {
command := opts.String("command")
if command == "" {
return core.Result{Value: core.E("process.run", "command is required", nil), OK: false}
}
runOpts := RunOptions{
Command: command,
Dir: opts.String("dir"),
}
if r := opts.Get("args"); r.OK {
runOpts.Args = optionStrings(r.Value)
}
if r := opts.Get("env"); r.OK {
runOpts.Env = optionStrings(r.Value)
}
return s.runCommand(ctx, runOpts)
}
func (s *Service) handleStart(ctx context.Context, opts core.Options) core.Result {
command := opts.String("command")
if command == "" {
return core.Result{Value: core.E("process.start", "command is required", nil), OK: false}
}
detach := true
if opts.Has("detach") {
detach = opts.Bool("detach")
}
runOpts := RunOptions{
Command: command,
Dir: opts.String("dir"),
Detach: detach,
}
if r := opts.Get("args"); r.OK {
runOpts.Args = optionStrings(r.Value)
}
if r := opts.Get("env"); r.OK {
runOpts.Env = optionStrings(r.Value)
}
r := s.StartWithOptions(ctx, runOpts)
if !r.OK {
return r
}
return core.Result{Value: r.Value.(*ManagedProcess).ID, OK: true}
}
func (s *Service) handleKill(ctx context.Context, opts core.Options) core.Result {
id := opts.String("id")
if id != "" {
if err := s.Kill(id); err != nil {
if core.Is(err, ErrProcessNotFound) {
return core.Result{Value: core.E("process.kill", core.Concat("not found: ", id), nil), OK: false}
}
return core.Result{Value: err, OK: false}
}
return core.Result{OK: true}
}
pid := opts.Int("pid")
if pid > 0 {
proc, err := processHandle(pid)
if err != nil {
return core.Result{Value: core.E("process.kill", core.Concat("find pid failed: ", core.Sprintf("%d", pid)), err), OK: false}
}
if err := proc.Signal(syscall.SIGTERM); err != nil {
return core.Result{Value: core.E("process.kill", core.Concat("signal failed: ", core.Sprintf("%d", pid)), err), OK: false}
}
return core.Result{OK: true}
}
return core.Result{Value: core.E("process.kill", "need id or pid", nil), OK: false}
}
func (s *Service) handleList(ctx context.Context, opts core.Options) core.Result {
return core.Result{Value: s.managed.Names(), OK: true}
}
func (s *Service) runCommand(ctx context.Context, opts RunOptions) core.Result {
if opts.Command == "" {
return core.Result{Value: core.E("process.run", "command is required", nil), OK: false}
@ -523,39 +440,6 @@ func isNotExist(err error) bool {
return os.IsNotExist(err)
}
func (s *Service) handleGet(ctx context.Context, opts core.Options) core.Result {
id := opts.String("id")
if id == "" {
return core.Result{Value: core.E("process.get", "id is required", nil), OK: false}
}
proc, err := s.Get(id)
if err != nil {
return core.Result{Value: core.E("process.get", core.Concat("not found: ", id), err), OK: false}
}
return core.Result{Value: proc.Info(), OK: true}
}
func optionStrings(value any) []string {
switch typed := value.(type) {
case nil:
return nil
case []string:
return append([]string(nil), typed...)
case []any:
result := make([]string, 0, len(typed))
for _, item := range typed {
text, ok := item.(string)
if !ok {
return nil
}
result = append(result, text)
}
return result
default:
return nil
}
}
func classifyProcessExit(proc *ManagedProcess, err error) (Status, int, error, string) {
if err == nil {
return StatusExited, 0, nil, ""

View file

@ -46,7 +46,7 @@ Exported fields:
### Package Functions
- `func Command(ctx context.Context, name string, args ...string) *Cmd`: Returns a `Cmd` for the supplied context, executable name, and arguments.
- `func RunQuiet(ctx context.Context, name string, args ...string) error`: Runs a command with stderr captured into a buffer and returns `core.E("RunQuiet", core.Trim(stderr.String()), err)` on failure.
- `func RunQuiet(ctx context.Context, name string, args ...string) error`: Runs a command with stderr captured into a buffer and returns `core.E("exec.run_quiet", core.Trim(stderr.String()), err)` on failure.
- `func SetDefaultLogger(l Logger)`: Sets the package-level default logger. Passing `nil` replaces it with `NopLogger`.
- `func DefaultLogger() Logger`: Returns the package-level default logger.
@ -58,9 +58,9 @@ Exported fields:
- `func (c *Cmd) WithStdout(w io.Writer) *Cmd`: Sets `Options.Stdout` and returns the same command.
- `func (c *Cmd) WithStderr(w io.Writer) *Cmd`: Sets `Options.Stderr` and returns the same command.
- `func (c *Cmd) WithLogger(l Logger) *Cmd`: Sets a command-specific logger and returns the same command.
- `func (c *Cmd) Run() error`: Prepares the underlying `exec.Cmd`, logs `"executing command"`, runs it, and wraps failures with `wrapError("Cmd.Run", ...)`.
- `func (c *Cmd) Output() ([]byte, error)`: Prepares the underlying `exec.Cmd`, logs `"executing command"`, returns stdout bytes, and wraps failures with `wrapError("Cmd.Output", ...)`.
- `func (c *Cmd) CombinedOutput() ([]byte, error)`: Prepares the underlying `exec.Cmd`, logs `"executing command"`, returns combined stdout and stderr, and wraps failures with `wrapError("Cmd.CombinedOutput", ...)`.
- `func (c *Cmd) Run() error`: Prepares the underlying `exec.Cmd`, logs `"executing command"`, runs it, and wraps failures with `wrapError("exec.cmd.run", ...)`.
- `func (c *Cmd) Output() ([]byte, error)`: Prepares the underlying `exec.Cmd`, logs `"executing command"`, returns stdout bytes, and wraps failures with `wrapError("exec.cmd.output", ...)`.
- `func (c *Cmd) CombinedOutput() ([]byte, error)`: Prepares the underlying `exec.Cmd`, logs `"executing command"`, returns combined stdout and stderr, and wraps failures with `wrapError("exec.cmd.combined_output", ...)`.
### `NopLogger` Methods