Start registers a long-running command with the group. It sets up graceful shutdown (SIGINT on context cancel, SIGKILL after timeout), wires stdout/stderr to structured logging, starts the process, and registers a goroutine that waits for it to exit.
(name string, cmd *exec.Cmd)
| 534 | // timeout), wires stdout/stderr to structured logging, starts the |
| 535 | // process, and registers a goroutine that waits for it to exit. |
| 536 | func (g *procGroup) Start(name string, cmd *exec.Cmd) error { |
| 537 | // Guard against nil env: appending to nil creates a non-nil |
| 538 | // slice that exec.Cmd treats as an explicit (empty) env. |
| 539 | if cmd.Env == nil { |
| 540 | cmd.Env = os.Environ() |
| 541 | } |
| 542 | cmd.Env = append(cmd.Env, "FORCE_COLOR=1") |
| 543 | |
| 544 | // Run in a new process group so signals reach the entire |
| 545 | // child tree (e.g. pnpm → vite), not just the direct child. |
| 546 | cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} |
| 547 | |
| 548 | // Graceful shutdown: SIGINT the process group on context |
| 549 | // cancel, escalate to SIGKILL after WaitDelay. |
| 550 | cmd.Cancel = func() error { |
| 551 | return syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) |
| 552 | } |
| 553 | cmd.WaitDelay = shutdownTimeout |
| 554 | |
| 555 | named := g.logger.Named(name) |
| 556 | w := &logWriter{logger: named} |
| 557 | cmd.Stdout = w |
| 558 | cmd.Stderr = w |
| 559 | |
| 560 | named.Info(g.ctx, "starting", slog.F("cmd", strings.Join(cmd.Args, " "))) |
| 561 | if err := cmd.Start(); err != nil { |
| 562 | return xerrors.Errorf("starting %s: %w", name, err) |
| 563 | } |
| 564 | |
| 565 | g.eg.Go(func() error { |
| 566 | err := cmd.Wait() |
| 567 | if err != nil { |
| 568 | return xerrors.Errorf("process %q exited: %w", name, err) |
| 569 | } |
| 570 | // Clean exit is still unexpected for a long-running dev |
| 571 | // process. Report it so the orchestrator shuts down. |
| 572 | return xerrors.Errorf("process %q exited unexpectedly", name) |
| 573 | }) |
| 574 | return nil |
| 575 | } |
| 576 | |
| 577 | // Wait blocks until all started processes have exited. |
| 578 | func (g *procGroup) Wait() error { return g.eg.Wait() } |