///|
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 ]")
}
_ => ()
}
}
///|
async fn main {
run_cli()
}
///|
fn inject_preview_assets(html : String) -> String {
let html = inject_preview_base(html)
let patch = preview_assets_for(html)
inject_assets(html, patch)
}
///|
fn inject_direct_assets(html : String) -> String {
let html = inject_preview_base(html)
let patch = preview_assets_for(html) + direct_client_for(html)
inject_assets(html, patch)
}
///|
fn inject_assets(html : String, patch : String) -> String {
guard patch != "" else { html }
let tag = match html.rev_find("") {
None => html.rev_find("