From c0036498bba6cbe6e9a5815ca7687cc57129376e Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 5 Jun 2024 12:16:36 +0200 Subject: replace unreliable dev server with modd --- cmd/dev/main.go | 340 -------------------------------------------------------- 1 file changed, 340 deletions(-) delete mode 100644 cmd/dev/main.go (limited to 'cmd/dev') diff --git a/cmd/dev/main.go b/cmd/dev/main.go deleted file mode 100644 index e1525a2..0000000 --- a/cmd/dev/main.go +++ /dev/null @@ -1,340 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "io/fs" - "net/http" - "net/http/httputil" - - "os" - "os/exec" - "os/signal" - "path" - "path/filepath" - "sync" - "syscall" - "time" - - "website/internal/config" - "website/internal/log" - - "github.com/antage/eventsource" - "github.com/ardanlabs/conf/v3" - "github.com/gohugoio/hugo/watcher" - - "github.com/pkg/errors" -) - -type DevConfig struct { - Source string `conf:"default:.,short:s"` - TempDir string `conf:"required,short:t"` - BaseURL config.URL `conf:"default:http://localhost:3000"` - ServerURL config.URL `conf:"default:http://localhost:3001"` -} - -func RunCommandPiped( - ctx context.Context, - command string, - args ...string, -) (cmd *exec.Cmd, err error) { - log.Debug("running command", "command", command, "args", args) - cmd = exec.CommandContext(ctx, command, args...) - cmd.Env = append(os.Environ(), "DEBUG=") - cmd.Cancel = func() error { - log.Debug("signalling child") - err := cmd.Process.Signal(os.Interrupt) - if err != nil { - log.Error("signal error:", "error", err) - } - - return errors.Wrap(err, "error signalling child process") - } - stdout, err := cmd.StdoutPipe() - if err != nil { - return - } - stderr, err := cmd.StderrPipe() - if err != nil { - return - } - - go func() { - _, err := io.Copy(os.Stdout, stdout) - if err != nil { - log.Error("error copying stdout", "error", err) - } - }() - go func() { - _, err := io.Copy(os.Stderr, stderr) - if err != nil { - log.Error("error copying stderr", "error", err) - } - }() - - return -} - -type FileWatcher struct { - *watcher.Batcher -} - -func NewFileWatcher(pollTime time.Duration) (*FileWatcher, error) { - batcher, err := watcher.New(pollTime/5, pollTime, true) - if err != nil { - return nil, errors.Wrap(err, "could not create file watcher") - } - - return &FileWatcher{batcher}, nil -} - -func (watcher FileWatcher) WatchAllFiles(from string) error { - log.Debug("watching files under", "from", from) - err := filepath.Walk(from, func(path string, _ fs.FileInfo, err error) error { - if err != nil { - return err - } - // log.Debug(fmt.Sprintf("adding file %s to watcher", path)) - if err = watcher.Add(path); err != nil { - return errors.Wrapf(err, "could not add path %s to watcher", path) - } - - return nil - }) - - return errors.Wrapf(err, "could not walk directory tree %s", from) -} - -func build(ctx context.Context, config DevConfig) error { - buildExe := filepath.Join(config.TempDir, "build") - cmd, err := RunCommandPiped(ctx, buildExe, - "--dest", path.Join(config.TempDir, "output"), - "--dev", - ) - // cmd, err := RunCommandPiped(ctx, "./devfakebuild") - - if err != nil { - return errors.WithMessage(err, "error running build command") - } - - err = cmd.Run() - log.Debug( - "build command exited", - "status", - cmd.ProcessState.ExitCode(), - ) - if err != nil { - return errors.WithMessage(err, "error running build command") - } - - return nil -} - -func server(ctx context.Context, devConfig DevConfig) error { - serverExe := path.Join(devConfig.TempDir, "server") - - cmd, err := RunCommandPiped(ctx, - serverExe, - "--port", devConfig.ServerURL.Port(), - "--root", path.Join(devConfig.TempDir, "output"), - "--in-dev-server", - ) - if err != nil { - return errors.WithMessage(err, "error running server command") - } - // cmd.Env = append(cmd.Env, "DEBUG=1") - - cmdErr := make(chan error, 1) - done := make(chan struct{}) - err = cmd.Start() - if err != nil { - return errors.WithMessage(err, "error starting server binary") - } - - go func() { - err := cmd.Wait() - if err == nil && cmd.ProcessState.Exited() { - err = errors.Errorf("server exited unexpectedly") - } - - cmdErr <- err - close(done) - }() - - for { - select { - case <-ctx.Done(): - log.Debug("server context done") - err := cmd.Process.Signal(os.Interrupt) - if err != nil { - return errors.Wrap(err, "could not process signal") - } - <-done - case err := <-cmdErr: - return err - } - } -} - -func main() { - var wg sync.WaitGroup - log.Configure(false) - - devConfig := DevConfig{} - help, err := conf.Parse("", &devConfig) - if err != nil { - if errors.Is(err, conf.ErrHelpWanted) { - fmt.Println(help) - os.Exit(1) - } - log.Panic("parsing dev configuration", "error", err) - } - - log.Debug("running with in /tmp", "dir", devConfig.TempDir) - - ctx, cancel := context.WithCancel(context.Background()) - - log.Debug("setting interrupt handler") - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - sig := <-c - log.Info("shutting down on signal", "sig", sig) - cancel() - sig = <-c - log.Info("got second signal, dying", "sig", sig) - os.Exit(1) - }() - - serverChan := make(chan bool, 1) - eventsource := eventsource.New(nil, nil) - srv := http.Server{ - Addr: devConfig.BaseURL.Host, - ReadHeaderTimeout: 1 * time.Minute, - } - devCtx, devCancel := context.WithTimeout(ctx, 1*time.Second) - - wg.Add(1) - go func() { - defer wg.Done() - defer devCancel() - log.Debug("waiting for first server launch") - <-serverChan - log.Debug("got first server launch event") - - http.Handle("/", &httputil.ReverseProxy{ - Rewrite: func(req *httputil.ProxyRequest) { - req.SetURL(devConfig.ServerURL.URL) - req.Out.Host = req.In.Host - }, - }) - http.Handle("/_/reload", eventsource) - done := make(chan bool) - go func() { - err := srv.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Error(err.Error()) - cancel() - } - done <- true - }() - go func() { - for ready := range serverChan { - if ready { - log.Debug("sending reload message") - eventsource.SendEventMessage("reload", "", "") - } else { - log.Debug("server not ready") - } - } - }() - log.Info("dev server listening on", "host", devConfig.BaseURL.String()) - <-done - log.Debug("dev server closed") - }() - - fw, err := NewFileWatcher(500 * time.Millisecond) - if err != nil { - log.Panic("error creating file watcher", "error", err) - } - err = fw.WatchAllFiles("content") - if err != nil { - log.Panic("could not watch files in content directory", "error", err) - } - err = fw.WatchAllFiles("templates") - if err != nil { - log.Panic("could not watch files in templates directory", "error", err) - } - - var exitCode int - serverErr := make(chan error, 1) -loop: - for { - serverCtx, stopServer := context.WithCancel(ctx) - log.Debug("starting build") - - err := build(ctx, devConfig) - if err != nil { - log.Error("build error:", "error", err) - // don't set up the server until there's a FS change event - } else { - log.Debug("setting up server") - wg.Add(1) - go func() { - defer wg.Done() - serverChan <- true - serverErr <- server(serverCtx, devConfig) - }() - } - - select { - case <-ctx.Done(): - log.Debug("main context cancelled") - log.Debug("calling server shutdown") - err := srv.Shutdown(devCtx) - if err != nil { - log.Debug("shutdown error", "error", err) - } - exitCode = 1 - - break loop - case event := <-fw.Events: - log.Debug("event received:", "event", event) - stopServer() - serverChan <- false - log.Debug("waiting for server shutdown") - <-serverErr - log.Debug("server shutdown completed") - - continue - case err = <-serverErr: - if err != nil && err != context.Canceled { - var exerr *exec.ExitError - log.Error("server reported error:", "error", err) - if errors.As(err, &exerr) { - log.Debug("server exit error") - } else { - log.Debug("server other error") - } - - break - } - log.Debug("no error or server context cancelled") - - continue - } - - log.Debug("waiting on server") - exitCode = 0 - - break - } - - log.Debug("waiting for wg before shutting down") - eventsource.Close() - cancel() - wg.Wait() - - os.Exit(exitCode) -} -- cgit 1.4.1