1// IMPORTS ---------------------------------------------------------------------
2
3import filepath
4import gleam/result
5import gleam/string
6import glint.{type Command}
7import lustre_dev_tools/cli.{type Cli, do, try}
8import lustre_dev_tools/cli/build
9import lustre_dev_tools/cli/flag
10import lustre_dev_tools/cmd
11import lustre_dev_tools/error.{type Error, CannotWriteFile}
12import lustre_dev_tools/project
13import lustre_dev_tools/server
14import simplifile
15
16// COMMANDS --------------------------------------------------------------------
17
18pub fn run() -> Command(Nil) {
19 let description =
20 "
21Start a development server for your Lustre project. This command will compile your
22application and serve it on a local server. If your application's `main` function
23returns a compatible `App`, this will generate the necessary code to start it.
24Otherwise, your `main` function will be used as the entry point.
25
26
27This development server does *not* currently watch your files for changes.
28Watchexec is a popular tool you can use to restart the server when files change.
29 "
30 use <- glint.command_help(description)
31 use <- glint.unnamed_args(glint.EqArgs(0))
32 use port <- glint.flag(flag.port())
33 use _proxy_from <- glint.flag(flag.proxy_from())
34 use _proxy_to <- glint.flag(flag.proxy_to())
35 use detect_tailwind <- glint.flag(flag.detect_tailwind())
36 use _tailwind_entry <- glint.flag(flag.tailwind_entry())
37 use _, _, flags <- glint.command()
38 let script = {
39 use port <- do(cli.get_int("port", 1234, ["start"], port))
40 use detect_tailwind <- do(cli.get_bool(
41 "detect_tailwind",
42 True,
43 ["build"],
44 detect_tailwind,
45 ))
46
47 use _ <- do(check_otp_version())
48 use _ <- do(build.do_app(False, detect_tailwind))
49 use _ <- do(prepare_html())
50 use _ <- do(server.start(port))
51
52 cli.return(Nil)
53 }
54
55 case cli.run(script, flags) {
56 Ok(_) -> Nil
57 Error(error) -> error.explain(error)
58 }
59}
60
61// STEPS -----------------------------------------------------------------------
62
63fn check_otp_version() -> Cli(Nil) {
64 use <- cli.log("Checking OTP version")
65 let version = project.otp_version()
66 case version <= 25 {
67 False -> cli.return(Nil)
68 True -> cli.throw(error.OtpTooOld(version))
69 }
70}
71
72fn prepare_html() -> Cli(Nil) {
73 let assert Ok(cwd) = cmd.cwd()
74 let assert Ok(root) = filepath.expand(filepath.join(cwd, project.root()))
75 let index = filepath.join(root, "index.html")
76
77 case simplifile.is_file(index) {
78 Ok(True) -> cli.return(Nil)
79 Ok(False) | Error(_) -> {
80 use html <- cli.template("index.html")
81 use app_name <- do(cli.get_name())
82 let html = string.replace(html, "{app_name}", app_name)
83 use _ <- try(write_html(index, html))
84
85 cli.return(Nil)
86 }
87 }
88}
89
90fn write_html(path: String, source: String) -> Result(Nil, Error) {
91 simplifile.write(path, source)
92 |> result.map_error(CannotWriteFile(_, path))
93}