///|
let shell_html_template =
  $|
  $|
  $|
  $|    
  $|    
  $|    warren dev
  $|    
  $|    
  $|
  $|
  $|    
$| $| ///| let app_html_template = $| $| $| $| $| $| warren preview $| $| $|
$| $| ///| let preview_base_patch = #| ///| let preview_entry_script_patch = #| ///| let preview_stylesheet_patch = #| ///| let direct_client_patch = #| ///| let direct_client_js = #|function connect() { #| const protocol = location.protocol === "https:" ? "wss:" : "ws:"; #| const socket = new WebSocket(`${protocol}//${location.host}/__warren/events`); #| socket.addEventListener("message", event => { #| if (event.data === "reload") { #| location.reload(); #| } #| }); #| socket.addEventListener("close", () => { #| setTimeout(connect, 1000); #| }); #|} #|connect(); ///| let fullstack_client_js = #|(function () { #| const script = document.currentScript; #| if (!script) { #| return; #| } #| const clientUrl = new URL(script.src); #| const protocol = clientUrl.protocol === "https:" ? "wss:" : "ws:"; #| const eventsUrl = `${protocol}//${clientUrl.host}/__warren/events`; #| function connect() { #| const socket = new WebSocket(eventsUrl); #| socket.addEventListener("message", event => { #| if (event.data === "reload") { #| location.reload(); #| } #| }); #| socket.addEventListener("close", () => { #| setTimeout(connect, 1000); #| }); #| } #| connect(); #|})(); ///| fn warren_cli() -> @argparse.Command { Command( "warren", options=[ OptionArg( "directory", short='C', global=true, about="Run as if Warren was started in this directory.", ), ], subcommands=[ Command( "dev", positionals=[ PositionArg("mbtx", about="Standalone .mbtx browser entry.", conflicts_with=[ "browser-entry", "server-entry", "server-target", ]), ], options=[ OptionArg( "browser-entry", about="Browser entry package directory. Default: ./cmd/browser; an empty value is invalid.", ), OptionArg( "server-entry", about="Server entry package directory. Default: ./cmd/server when present; pass an empty value to disable it.", ), OptionArg( "server-target", about="Server target: wasm (default) or native.", ), OptionArg( "public-dir", about="Static files directory. Defaults to ./public when present.", ), OptionArg("port", about="Local preview server port. Default: 4300."), ], flags=[ FlagArg( "direct", about="Serve the application as the top-level page and disable the server entry. Implies --server-entry \"\".", ), ], about="Start a local preview server with live reload.", ), Command( "build", positionals=[ PositionArg("mbtx", about="Standalone .mbtx browser entry.", conflicts_with=[ "browser-entry", "server-entry", "server-target", ]), ], options=[ OptionArg( "browser-entry", about="Browser entry package directory. Default: ./cmd/browser; an empty value is invalid.", ), OptionArg( "server-entry", about="Server entry package directory. Default: ./cmd/server when present; pass an empty value to disable it.", ), OptionArg( "server-target", about="Server target: wasm (default) or native.", ), OptionArg( "public-dir", about="Static files directory. Defaults to ./public when present.", ), OptionArg("dist", about="Build output directory. Default: ./dist."), ], about="Build browser and optional server release artifacts.", ), Command( "new", positionals=[ PositionArg( "dir_name", about="Directory name for the new Warren project.", ), ], options=[ OptionArg( "template", about="Project template to use. Default: default. Available: default, minimized.", ), ], about="Create a minimal Rabbita project.", ), ], about="Preview and build full-stack MoonBit applications.", ) } ///| fn option_value(matches : @argparse.Matches, name : String) -> String? { match matches.values.get(name) { Some([value]) => Some(value) _ => None } } ///| fn dev_server_entry_value(matches : @argparse.Matches) -> String? raise { let value = option_value(matches, "server-entry") if matches.flags.get("direct") is Some(true) { if value is Some(raw) && raw != "" { fail("--direct cannot be used with a non-empty --server-entry.") } Some("") } else { value } } ///| async fn run_cli() -> Unit { let cli = warren_cli().parse() guard cli.subcommand is Some((subcmd, submatches)) else { return } let directory = match option_value(submatches, "directory") { Some(path) => Some(path) None => option_value(cli, "directory") } let root = effective_root(directory) let mbtx_entry = option_value(submatches, "mbtx") match subcmd { "dev" => { let direct = submatches.flags.get("direct") is Some(true) let layout = if mbtx_entry is Some(raw) { resolve_mbtx_layout(root, raw, option_value(submatches, "public-dir")) } else { resolve_project_layout( root, option_value(submatches, "browser-entry"), dev_server_entry_value(submatches), option_value(submatches, "public-dir"), ) } let target_raw = option_value(submatches, "server-target") let server_target = parse_server_target(target_raw) if target_raw is Some(_) && layout.server_entry is None { fail("--server-target requires a server entry.") } let port = parse_port(option_value(submatches, "port")) dev_project(layout, server_target, port, direct) } "build" => { let layout = if mbtx_entry is Some(raw) { resolve_mbtx_layout(root, raw, option_value(submatches, "public-dir")) } else { resolve_project_layout( root, option_value(submatches, "browser-entry"), option_value(submatches, "server-entry"), option_value(submatches, "public-dir"), ) } let target_raw = option_value(submatches, "server-target") let server_target = parse_server_target(target_raw) if target_raw is Some(_) && layout.server_entry is None { fail("--server-target requires a server entry.") } let dist = resolve_dist_dir( root, option_value(submatches, "dist").unwrap_or("dist"), ) validate_dist_inputs( root, dist, layout.browser_entry, layout.server_entry, layout.public_dir, ) build_project(layout, server_target, dist) } "new" => match submatches.values.get("dir_name") { Some([dir]) => { let template = match submatches.values.get("template") { Some([name]) => name _ => default_new_project_template } new_project(root, dir, template) } _ => println("Usage: warren new [--template