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}