mirror of
https://github.com/maputnik/editor.git
synced 2026-08-25 22:47:39 +00:00
29cf3f1a97
Fixes #934 ## Summary The Go desktop binary started its local server and printed the URL, but never opened a browser automatically. This PR: - opens the default browser after the listener successfully binds - uses the actual runtime URL - uses a stdlib-only cross-platform launcher - keeps browser-open failures non-fatal - adds `--no-browser` for headless/Docker use - leaves the normal Vite/web development flow unchanged The listener is created before launching the browser so the browser cannot race the server startup path. ## Platform behavior - Windows: `rundll32 url.dll,FileProtocolHandler` - macOS: `open` - Linux: `xdg-open` No shell command strings are used; arguments are passed directly through `exec.Command`. ## Testing - `go test ./...` - `go vet ./...` - `go build ./...` - `git diff --check` - repeated manual Windows startup verification (3 clean runs, confirmed the browser opened and hit the server, no duplicate launches) - `--no-browser` verification (confirmed no launch attempt occurs) - non-fatal launcher failure covered by a unit test that injects a nonexistent opener binary macOS/Linux launch paths were not runtime-tested on this Windows machine — they follow the standard `open`/`xdg-open` convention used across other Go CLIs but are unverified here. The full packaged release flow (`gox`, `go.rice`, `go-winres`) was not exercised locally.
104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"github.com/GeertJohan/go.rice"
|
|
"github.com/gorilla/handlers"
|
|
"github.com/gorilla/mux"
|
|
"github.com/maputnik/desktop/filewatch"
|
|
"github.com/pkg/browser"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
func main() {
|
|
app := cli.NewApp()
|
|
app.Name = "maputnik"
|
|
app.Usage = "Server for integrating Maputnik locally"
|
|
app.Version = Version
|
|
|
|
app.Flags = []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "file, f",
|
|
Usage: "Allow access to JSON style from web client",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "watch",
|
|
Usage: "Notify web client about JSON style file changes",
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "port",
|
|
Value: 8000,
|
|
Usage: "TCP port to listen on",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "static",
|
|
Usage: "Serve directory under /static/",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "no-browser",
|
|
Usage: "Do not automatically open the default browser",
|
|
},
|
|
}
|
|
|
|
app.Action = func(c *cli.Context) error {
|
|
gui := http.FileServer(rice.MustFindBox("editor").HTTPBox())
|
|
|
|
router := mux.NewRouter().StrictSlash(true)
|
|
|
|
filename := c.String("file")
|
|
if filename != "" {
|
|
fmt.Printf("%s is accessible via Maputnik\n", filename)
|
|
// Allow access to reading and writing file on the local system
|
|
path, _ := filepath.Abs(filename)
|
|
accessor := StyleFileAccessor(path)
|
|
router.Path("/styles").Methods("GET").HandlerFunc(accessor.ListFiles)
|
|
router.Path("/styles/{styleId}").Methods("GET").HandlerFunc(accessor.ReadFile)
|
|
router.Path("/styles/{styleId}").Methods("PUT").HandlerFunc(accessor.SaveFile)
|
|
|
|
// Register websocket to notify we clients about file changes
|
|
if c.Bool("watch") {
|
|
router.Path("/ws").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
filewatch.ServeWebsocketFileWatcher(filename, w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
staticDir := c.String("static")
|
|
if staticDir != "" {
|
|
h := http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))
|
|
router.PathPrefix("/static/").Handler(h)
|
|
}
|
|
|
|
router.PathPrefix("/").Handler(http.StripPrefix("/", gui))
|
|
loggedRouter := handlers.LoggingHandler(os.Stdout, router)
|
|
corsRouter := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}), handlers.AllowedMethods([]string{"GET", "PUT"}), handlers.AllowedOrigins([]string{"*"}), handlers.AllowCredentials())(loggedRouter)
|
|
|
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", c.Int("port")))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
url := fmt.Sprintf("http://localhost:%d", c.Int("port"))
|
|
fmt.Printf("Exposing Maputnik on %s\n", url)
|
|
|
|
// Listener is already accepting connections, so this can't race http.Serve below.
|
|
// xdg-open is known to hang on some headless Linux setups, so this runs in its own
|
|
// goroutine to keep a stuck opener from stalling server startup.
|
|
if !c.Bool("no-browser") {
|
|
go func() {
|
|
if err := browser.OpenURL(url); err != nil {
|
|
fmt.Printf("Could not open browser automatically: %s\nPlease open %s manually.\n", err, url)
|
|
}
|
|
}()
|
|
}
|
|
|
|
return http.Serve(listener, corsRouter)
|
|
}
|
|
|
|
app.Run(os.Args)
|
|
}
|