package servercmd import ( "flag" "fmt" "io" "net/http" "os" "path/filepath" ) type Config struct { WorkspacesDir string ProjectRoot string Port int Addr string } type RunnerDeps struct { StdErr io.Writer InitRuntime func(Config) (io.Closer, error) BuildMux func(Config) http.Handler ListenAndServe func(string, http.Handler) error } func Run(args []string, deps RunnerDeps) error { cfg, err := parseConfig(args) if err != nil { return err } if deps.BuildMux == nil { return fmt.Errorf("server mux builder is not configured") } listenAndServe := deps.ListenAndServe if listenAndServe == nil { listenAndServe = http.ListenAndServe } stderr := deps.StdErr if stderr == nil { stderr = os.Stderr } return runWithConfig(cfg, deps, stderr, listenAndServe) } func parseConfig(args []string) (Config, error) { flagSet := flag.NewFlagSet("server", flag.ExitOnError) port := flagSet.Int("port", 3000, "HTTP server port") wsDir := flagSet.String("workspaces-dir", "", "directory containing workspace worktrees (required)") flagSet.Parse(args) if *wsDir == "" { return Config{}, fmt.Errorf("--workspaces-dir is required") } absDir, err := filepath.Abs(*wsDir) if err != nil { return Config{}, fmt.Errorf("resolving workspaces-dir: %w", err) } info, err := os.Stat(absDir) if err != nil || !info.IsDir() { return Config{}, fmt.Errorf("workspaces-dir does not exist or is not a directory: %s", absDir) } return Config{ WorkspacesDir: absDir, ProjectRoot: filepath.Dir(absDir), Port: *port, Addr: fmt.Sprintf(":%d", *port), }, nil } func runWithConfig(cfg Config, deps RunnerDeps, stderr io.Writer, listenAndServe func(string, http.Handler) error) error { var closer io.Closer var err error if deps.InitRuntime != nil { closer, err = deps.InitRuntime(cfg) if err != nil { return fmt.Errorf("opening central database: %w", err) } if closer != nil { defer closer.Close() } } handler := deps.BuildMux(cfg) fmt.Fprintf(stderr, "inbox API server listening on http://localhost%s\n", cfg.Addr) fmt.Fprintf(stderr, "workspaces dir: %s\n", cfg.WorkspacesDir) return listenAndServe(cfg.Addr, handler) }