1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::build::build_internal;
use crate::cmd::run_stage;
use crate::errors::*;
use console::{style, Emoji};
use std::env;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
static BUILDING_SERVER: Emoji<'_, '_> = Emoji("📡", "");
static SERVING: Emoji<'_, '_> = Emoji("🛰️ ", "");
macro_rules! handle_exit_code {
($code:expr) => {{
let (stdout, stderr, code) = $code;
if code != 0 {
return Ok(code);
}
(stdout, stderr)
}};
}
fn serve_internal(dir: PathBuf, did_build: bool) -> Result<i32> {
let num_steps = match did_build {
true => 5,
false => 2,
};
let mut target = dir;
target.extend([".perseus", "server"]);
let (stdout, _stderr) = handle_exit_code!(run_stage(
vec!["cargo build --message-format json"],
&target,
format!(
"{} {} Building server",
style(format!("[{}/{}]", num_steps - 1, num_steps))
.bold()
.dim(),
BUILDING_SERVER
)
)?);
let msgs: Vec<&str> = stdout.trim().split('\n').collect();
let msg = msgs.get(msgs.len() - 2);
let msg = match msg {
Some(msg) => serde_json::from_str::<serde_json::Value>(msg)
.map_err(|err| ErrorKind::GetServerExecutableFailed(err.to_string()))?,
None => bail!(ErrorKind::GetServerExecutableFailed(
"expected second-last message, none existed (too few messages)".to_string()
)),
};
let server_exec_path = msg.get("executable");
let server_exec_path = match server_exec_path {
Some(server_exec_path) => match server_exec_path.as_str() {
Some(server_exec_path) => server_exec_path,
None => bail!(ErrorKind::GetServerExecutableFailed(
"expected 'executable' field to be string".to_string()
)),
},
None => bail!(ErrorKind::GetServerExecutableFailed(
"expected 'executable' field in JSON map in second-last message, not present"
.to_string()
)),
};
let child = Command::new(server_exec_path)
.current_dir(target)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|err| ErrorKind::CmdExecFailed(server_exec_path.to_string(), err.to_string()))?;
let host = env::var("HOST").unwrap_or_else(|_| "localhost".to_string());
let port = env::var("PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse::<u16>()
.map_err(|err| ErrorKind::PortNotNumber(err.to_string()))?;
println!(
" {} {} Your app is now live on http://{host}:{port}! To change this, re-run this command with different settings of the HOST/PORT environment variables.",
style(format!("[{}/{}]", num_steps, num_steps)).bold().dim(),
SERVING,
host=host,
port=port
);
let output = child.wait_with_output().unwrap();
let exit_code = match output.status.code() {
Some(exit_code) => exit_code,
None if output.status.success() => 0,
None => 1,
};
if !output.stderr.is_empty() && exit_code != 0 {
std::io::stderr().write_all(&output.stderr).unwrap();
return Ok(1);
}
Ok(0)
}
pub fn serve(dir: PathBuf, prog_args: &[String]) -> Result<i32> {
let mut did_build = false;
if !prog_args.contains(&"--no-build".to_string()) {
did_build = true;
let build_exit_code = build_internal(dir.clone(), 4)?;
if build_exit_code != 0 {
return Ok(build_exit_code);
}
}
let exit_code = serve_internal(dir.clone(), did_build)?;
Ok(exit_code)
}