///|
using @path {type SourcePath, type Path}
///|
using @devhub {type Devhub}
///|
using @string {parse_int}
///|
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();
///|
async fn main {
let cli = @argparse.Command(
"warren",
subcommands=[
Command(
"dev",
positionals=[
PositionArg(
"main-package-dir",
about="Main package directory. Defaults to ./main when present, otherwise the current directory.",
),
],
options=[
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 without the Warren devtool iframe.",
),
],
about="Start a local preview server with live reload.",
),
Command(
"build",
positionals=[
PositionArg(
"main-package-dir",
about="Main package directory to build. Defaults to ./main when present, otherwise the current directory.",
),
],
options=[
OptionArg("dist", about="Build output directory. Default: ./dist."),
],
about="Build a release static site into ./dist by default.",
),
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 MoonBit web applications.",
).parse()
let cwd = @env.current_dir().unwrap()
guard cli.subcommand is Some((subcmd, submatches)) else { return }
match subcmd {
"dev" => {
let cwd_main = Path::join(cwd, "main").to_string()
let main_pkg_dir = match submatches.values.get("main-package-dir") {
Some([path]) => Path::resolve(path).to_string()
_ if @fs.exists(cwd_main) => cwd_main
_ => cwd
}
let cwd_public = Path::join(cwd, "public").to_string()
let public_dir = match submatches.values.get("public-dir") {
Some([path]) => Some(Path::resolve(path).to_string())
_ if @fs.exists(cwd_public) => Some(cwd_public)
_ => None
}
let port = match submatches.values.get("port") {
Some([raw]) => {
let port = parse_int(raw) catch {
_ =>
abort(
"Invalid --port value `\{raw}`. Expected a number from 1 to 65535.",
)
}
guard port > 0 && port <= 65535 else {
abort(
"Invalid --port value `\{raw}`. Expected a number from 1 to 65535.",
)
}
port.reinterpret_as_uint()
}
_ => 4300U
}
let direct = submatches.flags.get("direct") is Some(true)
let target_dir = @fs.tmpdir(prefix="warren")
dev_server(main_pkg_dir~, target_dir~, public_dir~, port~, direct~)
}
"build" => {
let cwd_main = Path::join(cwd, "main").to_string()
let main_pkg_dir = match submatches.values.get("main-package-dir") {
Some([path]) => Path::resolve(path).to_string()
_ if @fs.exists(cwd_main) => cwd_main
_ => cwd
}
let dist_dir = match submatches.values.get("dist") {
Some([path]) => Path::resolve(path).to_string()
_ => Path::join(cwd, "dist").to_string()
}
build_project(
SourcePath::new(main_pkg_dir),
dist_dir=SourcePath::new(dist_dir),
)
}
"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(dir, template)
}
_ => println("Usage: warren new [--template ]")
}
_ => ()
}
}
///|
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("