1// IMPORTS ---------------------------------------------------------------------
2
3import filepath
4import gleam/bool
5import gleam/erlang/process
6import gleam/http/request.{type Request, Request}
7import gleam/http/response.{type Response}
8import gleam/io
9import gleam/option.{None, Some}
10import gleam/regex
11import gleam/result
12import gleam/string_builder
13import lustre_dev_tools/cli.{type Cli, do, try}
14import lustre_dev_tools/cmd
15import lustre_dev_tools/error.{type Error, CannotStartDevServer}
16import lustre_dev_tools/project
17import lustre_dev_tools/server/live_reload
18import lustre_dev_tools/server/proxy
19import mist
20import simplifile
21import wisp
22
23pub fn start(port: Int) -> Cli(Nil) {
24 let assert Ok(cwd) = cmd.cwd()
25 let assert Ok(root) = filepath.expand(filepath.join(cwd, project.root()))
26
27 use proxy <- do(proxy.get())
28
29 case proxy {
30 Some(_) ->
31 io.println(
32 "
33[WARNING] Support for proxying requests to another server is currently still
34**experimental**. It's functionality or api may change is breaking ways even
35between minor versions. If you run into any problems please open an issue over
36at https://github.com/lustre-labs/dev-tools/issues/new
37 ",
38 )
39 None -> Nil
40 }
41 use flags <- do(cli.get_flags())
42
43 use make_socket <- try(live_reload.start(root, flags))
44 use _ <- try(
45 fn(req: Request(mist.Connection)) -> Response(mist.ResponseData) {
46 use <- proxy.middleware(req, proxy)
47
48 case request.path_segments(req) {
49 // We're going to inject a script that connects to /lustre-dev-tools over
50 // websockets. Whenever we detect a file change we can broadcast a reload
51 // message and get the client to hard refresh the page.
52 ["lustre-dev-tools"] -> make_socket(req)
53 [] ->
54 Request(..req, path: "/index.html")
55 |> wisp.mist_handler(handler(_, root), "")
56
57 // For everything else we're just going to serve any static files directly
58 // from the project's root.
59 _ -> wisp.mist_handler(handler(_, root), "")(req)
60 }
61 }
62 |> mist.new
63 |> mist.port(port)
64 |> mist.start_http
65 |> result.map_error(CannotStartDevServer),
66 )
67
68 cli.return(process.sleep_forever())
69}
70
71fn handler(req: wisp.Request, root: String) -> wisp.Response {
72 use <- inject_live_reload(req, root)
73 use <- wisp.serve_static(req, under: "/", from: root)
74
75 handler(Request(..req, path: "/index.html"), root)
76}
77
78fn inject_live_reload(
79 req: wisp.Request,
80 root: String,
81 k: fn() -> wisp.Response,
82) -> wisp.Response {
83 let assert Ok(is_interesting) = regex.from_string(".*\\.html$")
84 use <- bool.lazy_guard(!regex.check(is_interesting, req.path), k)
85 let path = filepath.join(root, req.path)
86
87 case simplifile.is_file(path) {
88 Ok(False) | Error(_) -> k()
89 Ok(True) -> {
90 let assert Ok(html) = simplifile.read(path)
91
92 html
93 |> live_reload.inject
94 |> string_builder.from_string
95 |> wisp.html_response(200)
96 }
97 }
98}