package cli import ( "context" "fmt" "net" "net/http" "os" "path/filepath" "time" "github.com/spf13/cobra" ) func newServeCmd() *cobra.Command { var addr string cmd := &cobra.Command{ Use: "serve [dir]", Short: "Preview a cloned folder in your browser", Long: "serve runs a local static file server over a cloned folder so you can click\n" + "through the mirror exactly as a visitor would. With no dir it serves the\n" + "current directory.", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { dir := "." if len(args) == 1 { dir = args[0] } return runServe(cmd.Context(), dir, addr) }, } cmd.Flags().StringVarP(&addr, "addr", "a", "127.0.0.1:8800", "address to listen on") return cmd } func runServe(ctx context.Context, dir, addr string) error { info, err := os.Stat(dir) if err != nil { return fmt.Errorf("cannot serve %q: %w", dir, err) } if !info.IsDir() { return fmt.Errorf("%q is not a directory", dir) } abs, _ := filepath.Abs(dir) ln, err := net.Listen("tcp", addr) if err != nil { return fmt.Errorf("cannot listen on %s: %w", addr, err) } srv := &http.Server{Handler: http.FileServer(http.Dir(abs))} fmt.Fprintln(os.Stderr, styleTitle.Render("kage serve")+" "+styleDim.Render(abs)) fmt.Fprintln(os.Stderr, " open "+styleAccent.Render("http://"+ln.Addr().String())) fmt.Fprintln(os.Stderr, styleDim.Render(" press Ctrl-C to stop")) srvErr := make(chan error, 1) go func() { srvErr <- srv.Serve(ln) }() select { case err := <-srvErr: if err != nil && err != http.ErrServerClosed { return err } case <-ctx.Done(): shutdownCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { _ = srv.Close() } if err := <-srvErr; err != nil && err != http.ErrServerClosed { return err } } return nil }