diff options
Diffstat (limited to 'src/lustre/cli/dev.gleam')
-rw-r--r-- | src/lustre/cli/dev.gleam | 245 |
1 files changed, 0 insertions, 245 deletions
diff --git a/src/lustre/cli/dev.gleam b/src/lustre/cli/dev.gleam deleted file mode 100644 index f7e12c2..0000000 --- a/src/lustre/cli/dev.gleam +++ /dev/null @@ -1,245 +0,0 @@ -// IMPORTS --------------------------------------------------------------------- - -import filepath -import gleam/dict -import gleam/io -import gleam/package_interface.{type Type, Fn, Named, Variable} -import gleam/result -import gleam/string -import glint.{type Command, CommandInput} -import glint/flag -import lustre/cli/esbuild -import lustre/cli/project.{type Module} -import lustre/cli/step -import lustre/cli/utils.{guard, keep, map, replace, template, try} -import simplifile - -// COMMANDS -------------------------------------------------------------------- - -pub fn run() -> Command(Nil) { - let description = - " - " - - glint.command(fn(input) { - let CommandInput(flags: flags, ..) = input - let assert Ok(host) = flag.get_string(flags, "host") - let assert Ok(port) = flag.get_string(flags, "port") - let assert Ok(use_lustre_ui) = flag.get_bool(flags, "use-lustre-ui") - let assert Ok(spa) = flag.get_bool(flags, "spa") - let custom_html = flag.get_string(flags, "html") - - let script = { - use <- step.new("Building your project") - use interface <- step.try(project.interface(), replace(BuildError)) - use module <- step.try( - dict.get(interface.modules, interface.name), - replace(ModuleMissing(interface.name)), - ) - use is_app <- step.try(check_is_lustre_app(interface.name, module), keep) - use <- step.done("✅ Project compiled successfully") - - use <- step.new("Creating the application entry point") - let root = project.root() - let tempdir = filepath.join(root, "build/.lustre") - let _ = simplifile.create_directory_all(tempdir) - - let entry = - template(case is_app { - True -> "entry-with-start.mjs" - False -> "entry-with-main.mjs" - }) - |> string.replace("{app_name}", interface.name) - - use html <- step.try( - case custom_html { - Ok(custom_html_path) -> - custom_html_path - |> simplifile.read - |> result.map_error(CouldntOpenCustomHtml(_, custom_html_path)) - |> result.map(string.replace( - _, - "<script type=\"application/lustre\">", - "<script type=\"module\" src=\"./index.mjs\">", - )) - - Error(_) if use_lustre_ui -> - template("index-with-lustre-ui.html") - |> string.replace("{app_name}", interface.name) - |> Ok - - _ -> - template("index.html") - |> string.replace("{app_name}", interface.name) - |> Ok - }, - keep, - ) - - let assert Ok(_) = simplifile.write(tempdir <> "/entry.mjs", entry) - let assert Ok(_) = simplifile.write(tempdir <> "/index.html", html) - - use _ <- step.run( - esbuild.bundle( - filepath.join(tempdir, "entry.mjs"), - filepath.join(tempdir, "index.mjs"), - False, - ), - map(BundleError), - ) - use _ <- step.run(esbuild.serve(host, port, spa), map(BundleError)) - step.return(Nil) - } - - case step.execute(script) { - Ok(_) -> Nil - Error(error) -> explain(error) - } - }) - |> glint.description(description) - |> glint.unnamed_args(glint.EqArgs(0)) - |> glint.flag("host", { - let description = "" - let default = "localhost" - - flag.string() - |> flag.default(default) - |> flag.description(description) - }) - |> glint.flag("port", { - let description = "" - let default = "1234" - - flag.string() - |> flag.default(default) - |> flag.description(description) - }) - |> glint.flag("use-lustre-ui", { - let description = "Inject lustre/ui's stylesheet. Ignored if --html is set." - let default = False - - flag.bool() - |> flag.default(default) - |> flag.description(description) - }) - |> glint.flag("spa", { - let description = - "Serve your app on any route. Useful for apps that do client-side routing." - let default = False - - flag.bool() - |> flag.default(default) - |> flag.description(description) - }) - |> glint.flag("html", { - let description = - "Supply a custom HTML file to use as the entry point. -To inject the Lustre bundle, make sure it includes the following empty script: -<script type=\"application/lustre\"></script> - " - |> string.trim_right - - flag.string() - |> flag.description(description) - }) -} - -// ERROR HANDLING -------------------------------------------------------------- - -type Error { - BuildError - BundleError(esbuild.Error) - CouldntOpenCustomHtml(error: simplifile.FileError, path: String) - MainMissing(module: String) - MainIncorrectType(module: String, got: Type) - MainBadAppType(module: String, got: Type) - ModuleMissing(module: String) -} - -fn explain(error: Error) -> Nil { - case error { - BuildError -> project.explain(project.BuildError) - - BundleError(error) -> esbuild.explain(error) - - CouldntOpenCustomHtml(_, path) -> io.println(" -I couldn't open the custom HTML file at `" <> path <> "`.") - - MainMissing(module) -> io.println(" -Module `" <> module <> "` doesn't have a public `main` function I can preview.") - - MainIncorrectType(module, type_) -> io.println(" -I cannot preview the `main` function exposed by module `" <> module <> "`. -To start a preview server I need it to take no arguments and return a Lustre -`App`. -The one I found has type `" <> project.type_to_string(type_) <> "`.") - - // TODO: maybe this could have useful links to `App`/flags... - MainBadAppType(module, type_) -> io.println(" -I cannot preview the `main` function exposed by module `" <> module <> "`. -To start a preview server I need it to return a Lustre `App` that doesn't need -any flags. -The one I found has type `" <> project.type_to_string(type_) <> "`. - -Its return type should look something like this: - - import lustre.{type App} - pub fn main() -> App(flags, model, msg) { - todo as \"your Lustre application to preview\" - }") - - ModuleMissing(module) -> io.println(" -I couldn't find a public module called `" <> module <> "` in your project.") - } -} - -// STEPS ----------------------------------------------------------------------- - -fn check_is_lustre_app( - module_path: String, - module: Module, -) -> Result(Bool, Error) { - use main <- try( - dict.get(module.functions, "main"), - replace(MainMissing(module_path)), - ) - use <- guard( - main.parameters != [], - MainIncorrectType(module_path, Fn(main.parameters, main.return)), - ) - - case main.return { - Named( - name: "App", - package: "lustre", - module: "lustre", - parameters: [flags, ..], - ) -> - case is_compatible_flags_type(flags) { - True -> Ok(True) - False -> Error(MainBadAppType(module_path, main.return)) - } - - _ -> Ok(False) - } -} - -// UTILS ----------------------------------------------------------------------- - -fn is_nil_type(t: Type) -> Bool { - case t { - Named(name: "Nil", package: "", module: "gleam", parameters: []) -> True - _ -> False - } -} - -fn is_type_variable(t: Type) -> Bool { - case t { - Variable(..) -> True - _ -> False - } -} - -fn is_compatible_flags_type(t: Type) -> Bool { - is_nil_type(t) || is_type_variable(t) -} |