All checks were successful
Beta Release / beta (push) Successful in 34s
Remove 5 unused packages (daemon, preview, proxy, workflow) and dead symbols across 7 files: orchestrator workflow engine, skills Target type and Update(), LSP config generation, installer SetupPrompt(), unexported desktop options, and version License/Prerelease. Total: -1453 lines.
132 lines
2.6 KiB
Go
132 lines
2.6 KiB
Go
package desktop
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/muyue/muyue/internal/api"
|
|
"github.com/muyue/muyue/internal/config"
|
|
"github.com/muyue/muyue/internal/version"
|
|
"github.com/muyue/muyue/web"
|
|
)
|
|
|
|
type options struct {
|
|
port int
|
|
noOpen bool
|
|
}
|
|
|
|
type option func(*options)
|
|
|
|
func withPort(port int) option {
|
|
return func(o *options) { o.port = port }
|
|
}
|
|
|
|
func withNoOpen(noOpen bool) option {
|
|
return func(o *options) { o.noOpen = noOpen }
|
|
}
|
|
|
|
func parseFlags(args []string) []option {
|
|
var opts []option
|
|
for _, arg := range args {
|
|
switch {
|
|
case arg == "--no-open":
|
|
opts = append(opts, withNoOpen(true))
|
|
case strings.HasPrefix(arg, "--port="):
|
|
if p, err := strconv.Atoi(strings.TrimPrefix(arg, "--port=")); err == nil {
|
|
opts = append(opts, withPort(p))
|
|
}
|
|
case arg == "--port":
|
|
// handled as prefix case
|
|
}
|
|
}
|
|
return opts
|
|
}
|
|
|
|
func Run(cfg *config.MuyueConfig, args []string) error {
|
|
o := options{}
|
|
for _, opt := range parseFlags(args) {
|
|
opt(&o)
|
|
}
|
|
|
|
log.Printf("%s Desktop v%s", version.Name, version.Version)
|
|
|
|
srv := api.NewServer(cfg)
|
|
|
|
frontendFS, err := fs.Sub(web.Assets, "dist")
|
|
if err != nil {
|
|
return fmt.Errorf("frontend assets: %w", err)
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/api/", srv)
|
|
mux.Handle("/", spaHandler(http.FileServer(http.FS(frontendFS))))
|
|
|
|
addr := fmt.Sprintf("127.0.0.1:%d", o.port)
|
|
listener, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
return fmt.Errorf("bind %s: %w", addr, err)
|
|
}
|
|
port := listener.Addr().(*net.TCPAddr).Port
|
|
|
|
go func() {
|
|
if err := http.Serve(listener, mux); err != nil {
|
|
log.Fatalf("Server error: %v", err)
|
|
}
|
|
}()
|
|
|
|
url := fmt.Sprintf("http://127.0.0.1:%d", port)
|
|
log.Printf("Listening on %s", url)
|
|
|
|
if !o.noOpen {
|
|
openBrowser(url)
|
|
log.Printf("Opened browser")
|
|
}
|
|
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
<-quit
|
|
|
|
log.Println("Shutting down...")
|
|
return nil
|
|
}
|
|
|
|
func spaHandler(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
if path != "/" && !strings.Contains(path, ".") {
|
|
r.URL.Path = "/"
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func openBrowser(url string) {
|
|
var cmd *exec.Cmd
|
|
switch {
|
|
case exists("xdg-open"):
|
|
cmd = exec.Command("xdg-open", url)
|
|
case exists("open"):
|
|
cmd = exec.Command("open", url)
|
|
case exists("cmd"):
|
|
cmd = exec.Command("cmd", "/c", "start", url)
|
|
default:
|
|
fmt.Printf("Open manually: %s\n", url)
|
|
return
|
|
}
|
|
_ = cmd.Start()
|
|
}
|
|
|
|
func exists(name string) bool {
|
|
_, err := exec.LookPath(name)
|
|
return err == nil
|
|
}
|