livid_cli/
lib.rs

1use livid_server::Server;
2use std::path::PathBuf;
3use std::process::Command;
4
5const USAGE: &str = r#"{{BIN}} {{VERSION}}
6Builds and bundles your wasm web app.
7USAGE:
8    {{BIN}} <SUBCOMMAND>
9SUBCOMMANDS:
10    build     Build your wasm web app
11    clean     Clean output artifacts
12    serve     Serve the generated index.html
13    deploy    Creates a desktop app using the wasm web app for frontend
14    --help    Prints this message
15"#;
16
17const DEPLOY: &str = r#"USAGE:
18    {{BIN}} deploy <OPTIONS>
19OPTIONS:
20    --width   Sets the window's width
21    --height  Sets the window's height
22    --title   Sets the window's title
23    --port    Sets the server's local port
24    --using   Sets the project to be used for the backend
25    --help    Prints this message
26"#;
27
28const SCRIPT: &str = r#"
29<script src="./{{crate}}.js"></script>
30<script type="module">
31  import init from "./{{crate}}.js";
32  init();
33</script>
34"#;
35
36const HTML: &str = r#"
37<html>
38  <head>
39  <meta charset="utf-8">
40  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
41  <meta name="viewport" content="width=device-width, initial-scale=1.0">
42  </head>
43  <body>
44    {{SCRIPT}}
45  </body>
46</html>
47"#;
48
49const APP: &str = r#"use livid_desktop::{App, Settings};
50
51fn main() {
52    let a = App::new(Settings {
53        w: {{width}},
54        h: {{height}},
55        title: {{title}},
56        port: {{port}},
57        ..Default::default()
58    });
59    a.run();
60}"#;
61
62const CARGO: &str = r#"[package]
63name = "{{crate}}"
64version = "0.1.0"
65edition = "2021"
66
67[dependencies]
68livid-desktop = "0.1"
69
70[profile.release]
71opt-level = 3
72strip = true
73"#;
74
75pub fn handle_args(bin_name: &str, args: &[String]) {
76    if args.len() == 1 {
77        help(bin_name);
78        return;
79    }
80    match args[1].as_str() {
81        "build" => build(args),
82        "serve" => serve(args),
83        "deploy" => deploy(bin_name, args),
84        "clean" => clean(),
85        "--help" | "--version" => help(bin_name),
86        _ => help(bin_name),
87    }
88}
89
90fn check_prog(prog: &str) -> bool {
91    let mut cmd = Command::new(prog);
92    cmd.args(["--help"]);
93    cmd.output().is_ok()
94}
95
96fn help(bin_name: &str) {
97    let usage = USAGE
98        .replace("{{BIN}}", bin_name)
99        .replace("{{VERSION}}", env!("CARGO_PKG_VERSION"));
100    println!("{}", usage);
101}
102
103fn clean() {
104    let mut cargo = Command::new("cargo");
105    cargo.args(["clean"]);
106    cargo.spawn().unwrap().wait().unwrap();
107
108    let dist = PathBuf::from("dist");
109    if dist.exists() {
110        std::fs::remove_dir_all(dist).unwrap();
111    }
112}
113
114fn serve(args: &[String]) {
115    build(args);
116    println!("Livid server running on http://0.0.0.0:8080!\nServing dist/");
117    let mut server = Server::new(8080);
118    server.serve_dir(&std::env::current_dir().unwrap().join("dist"));
119    server.serve();
120}
121
122fn build(args: &[String]) {
123    let mut release = false;
124    if let Some(val) = args.get(2) {
125        if val == "--release" {
126            release = true;
127        }
128    }
129    let cargo_toml = std::fs::read_to_string("Cargo.toml").expect("Failed to find a Cargo.toml!");
130    let pkg: toml::Value = cargo_toml.parse().unwrap();
131    let crate_name = format!("{}", pkg["package"]["name"]).replace('"', "");
132    let mut path = PathBuf::from("target").join("wasm32-unknown-unknown");
133    if release {
134        path = path.join("release");
135    } else {
136        path = path.join("debug");
137    }
138    path = path.join(format!("{}.wasm", &crate_name));
139    if !check_prog("wasm-bindgen") {
140        eprintln!("wasm-bindgen-cli was not found, running a first-time install...");
141        let mut cargo = Command::new("cargo");
142        cargo.args(["install", "wasm-bindgen-cli"]);
143        cargo.spawn().unwrap().wait().unwrap();
144    }
145    let mut cargo = std::process::Command::new("cargo");
146    let mut cargo_args = vec!["build", "--target", "wasm32-unknown-unknown"];
147    if release {
148        cargo_args.push("--release");
149    }
150    cargo.args(&cargo_args);
151    cargo.spawn().unwrap().wait().unwrap();
152    if check_prog("wasm-opt") && release {
153        let mut opt = Command::new("wasm-opt");
154        let path = format!("{}", path.display());
155        opt.args([&path, "-O3", "-o", &path]);
156        opt.spawn().unwrap().wait().unwrap();
157    }
158    let mut wb = Command::new("wasm-bindgen");
159    wb.args([
160        &format!("{}", path.display()),
161        "--out-dir",
162        "dist",
163        "--target",
164        "web",
165        "--weak-refs",
166        "--no-typescript",
167    ]);
168    wb.spawn().unwrap().wait().unwrap();
169    let script = SCRIPT.to_string().replace("{{crate}}", &crate_name);
170    let html = std::fs::read_to_string("index.html")
171        .unwrap_or_else(|_| HTML.to_string())
172        .replace("{{SCRIPT}}", &script);
173    let dist = PathBuf::from("dist");
174    if dist.exists() {
175        std::fs::write(dist.join("index.html"), html).unwrap();
176    }
177}
178
179fn deploy(bin_name: &str, args: &[String]) {
180    let mut w = 600;
181    let mut h = 400;
182    let mut title = "my app".to_string();
183    let mut port = "8080".to_string();
184    let mut proj_path = None;
185    for arg in args {
186        if arg == "--help" {
187            println!("{}", DEPLOY.replace("{{BIN}}", bin_name));
188            return;
189        }
190        if let Some(using) = arg.strip_prefix("--using=") {
191            proj_path = Some(PathBuf::from(using));
192            break;
193        }
194        if let Some(width) = arg.strip_prefix("--width=") {
195            w = width.parse().unwrap();
196        }
197        if let Some(height) = arg.strip_prefix("--height=") {
198            h = height.parse().unwrap();
199        }
200        if let Some(t) = arg.strip_prefix("--title=") {
201            title = t.to_string();
202        }
203        if let Some(p) = arg.strip_prefix("--port=") {
204            port = p.to_string();
205        }
206    }
207    build(&[String::new(), String::new(), "--release".to_string()]);
208    let app = APP
209        .to_string()
210        .replace("{{width}}", &w.to_string())
211        .replace("{{height}}", &h.to_string())
212        .replace("{{port}}", &format!("\"{}\"", port))
213        .replace("{{title}}", &format!("\"{}\"", title));
214    let cargo_toml = std::fs::read_to_string("Cargo.toml").expect("Failed to find a Cargo.toml!");
215    let pkg: toml::Value = cargo_toml.parse().unwrap();
216    let mut crate_name = format!("{}", pkg["package"]["name"]).replace('"', "");
217    let cargo_toml = CARGO.to_string().replace("{{crate}}", &crate_name);
218    let temp_dir = std::env::temp_dir();
219    let proj = if let Some(proj_path) = proj_path {
220        let cargo_toml = std::fs::read_to_string(proj_path.join("Cargo.toml"))
221            .expect("Failed to find a Cargo.toml!");
222        let pkg: toml::Value = cargo_toml.parse().unwrap();
223        crate_name = format!("{}", pkg["package"]["name"]).replace('"', "");
224        proj_path
225    } else {
226        temp_dir.join("livid_temp")
227    };
228    if !proj.exists() {
229        let mut cargo = Command::new("cargo");
230        cargo.current_dir(&temp_dir);
231        cargo.args(["new", "livid_temp"]);
232        cargo.spawn().unwrap().wait().unwrap();
233        std::fs::write(proj.join("src").join("main.rs"), app).unwrap();
234        std::fs::write(proj.join("Cargo.toml"), cargo_toml).unwrap();
235    }
236    let mut cargo = Command::new("cargo");
237    cargo.current_dir(&proj);
238    cargo.args(["build", "--release"]);
239    cargo.spawn().unwrap().wait().unwrap();
240    let cwd = std::env::current_dir().unwrap();
241    let bundle = cwd.join("bundle");
242    if !bundle.exists() {
243        std::fs::create_dir(&bundle).unwrap();
244    }
245    let exe = if cfg!(target_os = "windows") {
246        crate_name.push_str(".exe");
247        crate_name
248    } else {
249        crate_name
250    };
251    std::fs::copy(
252        proj.join("target").join("release").join(&exe),
253        bundle.join(exe),
254    )
255    .unwrap();
256    let mut opts = fs_extra::dir::CopyOptions::new();
257    opts.overwrite = true;
258    opts.copy_inside = true;
259    fs_extra::dir::copy(cwd.join("dist"), cwd.join("bundle"), &opts).unwrap();
260}