chore(repo): reinitialize repository
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package servercmd_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
servercmd "inbox/internal/servercmd"
|
||||
)
|
||||
|
||||
func TestRunValidatesWorkspaceDir(t *testing.T) {
|
||||
if err := servercmd.Run([]string{}, servercmd.RunnerDeps{}); err == nil || !strings.Contains(err.Error(), "--workspaces-dir is required") {
|
||||
t.Fatalf("Run missing dir error = %v", err)
|
||||
}
|
||||
|
||||
missing := t.TempDir() + "/missing"
|
||||
if err := servercmd.Run([]string{"--workspaces-dir", missing}, servercmd.RunnerDeps{}); err == nil || !strings.Contains(err.Error(), "workspaces-dir does not exist") {
|
||||
t.Fatalf("Run invalid dir error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunInitializesRuntimeBuildsMuxAndCallsServer(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sentinel := errors.New("stop server")
|
||||
var gotAddr string
|
||||
var gotConfig servercmd.Config
|
||||
var initConfig servercmd.Config
|
||||
var stderr bytes.Buffer
|
||||
|
||||
err := servercmd.Run([]string{"--workspaces-dir", root, "--port", "4321"}, servercmd.RunnerDeps{
|
||||
StdErr: stderrWriter{&stderr},
|
||||
InitRuntime: func(cfg servercmd.Config) (io.Closer, error) {
|
||||
initConfig = cfg
|
||||
return nil, nil
|
||||
},
|
||||
BuildMux: func(cfg servercmd.Config) http.Handler {
|
||||
gotConfig = cfg
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusTeapot)
|
||||
})
|
||||
},
|
||||
ListenAndServe: func(addr string, handler http.Handler) error {
|
||||
gotAddr = addr
|
||||
return sentinel
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, sentinel) {
|
||||
t.Fatalf("Run error = %v, want sentinel", err)
|
||||
}
|
||||
if gotAddr != ":4321" {
|
||||
t.Fatalf("listen addr = %q, want :4321", gotAddr)
|
||||
}
|
||||
if gotConfig.WorkspacesDir != root {
|
||||
t.Fatalf("build mux workspaces dir = %q, want %q", gotConfig.WorkspacesDir, root)
|
||||
}
|
||||
if gotConfig.Port != 4321 || gotConfig.Addr != ":4321" {
|
||||
t.Fatalf("unexpected build mux config = %#v", gotConfig)
|
||||
}
|
||||
if initConfig.ProjectRoot != filepath.Dir(root) {
|
||||
t.Fatalf("init runtime project root = %q, want %q", initConfig.ProjectRoot, filepath.Dir(root))
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "inbox API server listening on http://localhost:4321") {
|
||||
t.Fatalf("stderr = %q, want startup log", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
type stderrWriter struct {
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func (w stderrWriter) Write(p []byte) (int, error) {
|
||||
if w.buf == nil {
|
||||
return len(p), nil
|
||||
}
|
||||
return w.buf.Write(p)
|
||||
}
|
||||
Reference in New Issue
Block a user