Skip to content

Commit 8274678

Browse files
committed
feat(cli): added inbuilt server for exported apps
No more `serve`!
1 parent dccd25e commit 8274678

File tree

7 files changed

+73
-11
lines changed

7 files changed

+73
-11
lines changed

packages/perseus-cli/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ serde = "1"
2929
serde_json = "1"
3030
clap = { version = "=3.0.0-beta.5", features = ["color"] }
3131
fs_extra = "1"
32+
tokio = { version = "1", features = [ "macros", "rt-multi-thread" ] }
33+
warp = "0.3"
3234

3335
[lib]
3436
name = "perseus_cli"

packages/perseus-cli/src/bin/main.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,28 @@ use perseus_cli::parse::SnoopSubcommand;
44
use perseus_cli::{
55
build, check_env, delete_artifacts, delete_bad_dir, deploy, eject, export, has_ejected,
66
parse::{Opts, Subcommand},
7-
prepare, serve, tinker,
7+
prepare, serve, serve_exported, tinker,
88
};
99
use perseus_cli::{errors::*, snoop_build, snoop_server, snoop_wasm_build};
1010
use std::env;
1111
use std::io::Write;
1212
use std::path::PathBuf;
1313

1414
// All this does is run the program and terminate with the acquired exit code
15-
fn main() {
15+
#[tokio::main]
16+
async fn main() {
1617
// In development, we'll test in the `basic` example
1718
if cfg!(debug_assertions) {
1819
let example_to_test =
1920
env::var("TEST_EXAMPLE").unwrap_or_else(|_| "../../examples/basic".to_string());
2021
env::set_current_dir(example_to_test).unwrap();
2122
}
22-
let exit_code = real_main();
23+
let exit_code = real_main().await;
2324
std::process::exit(exit_code)
2425
}
2526

2627
// This manages error handling and returns a definite exit code to terminate with
27-
fn real_main() -> i32 {
28+
async fn real_main() -> i32 {
2829
// Get the working directory
2930
let dir = env::current_dir();
3031
let dir = match dir {
@@ -37,7 +38,7 @@ fn real_main() -> i32 {
3738
return 1;
3839
}
3940
};
40-
let res = core(dir.clone());
41+
let res = core(dir.clone()).await;
4142
match res {
4243
// If it worked, we pass the executed command's exit code through
4344
Ok(exit_code) => exit_code,
@@ -60,7 +61,7 @@ fn real_main() -> i32 {
6061
// This returns the exit code of the executed command, which we should return from the process itself
6162
// This prints warnings using the `writeln!` macro, which allows the parsing of `stdout` in production or a vector in testing
6263
// If at any point a warning can't be printed, the program will panic
63-
fn core(dir: PathBuf) -> Result<i32, Error> {
64+
async fn core(dir: PathBuf) -> Result<i32, Error> {
6465
// Get `stdout` so we can write warnings appropriately
6566
let stdout = &mut std::io::stdout();
6667

@@ -88,7 +89,16 @@ fn core(dir: PathBuf) -> Result<i32, Error> {
8889
// Delete old build/exportation artifacts
8990
delete_artifacts(dir.clone(), "static")?;
9091
delete_artifacts(dir.clone(), "exported")?;
91-
export(dir, export_opts)?
92+
let exit_code = export(dir.clone(), export_opts.clone())?;
93+
if exit_code != 0 {
94+
return Ok(exit_code);
95+
}
96+
// Start a server for those files if requested
97+
if export_opts.serve {
98+
serve_exported(dir, export_opts.host, export_opts.port).await;
99+
}
100+
101+
0
92102
}
93103
Subcommand::Serve(serve_opts) => {
94104
// Delete old build artifacts if `--no-build` wasn't specified

packages/perseus-cli/src/deploy.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,15 @@ fn deploy_full(dir: PathBuf, output: String, integration: Integration) -> Result
128128
/// subcommands.
129129
fn deploy_export(dir: PathBuf, output: String) -> Result<i32, Error> {
130130
// Export the app to `.perseus/exported`, using release mode
131-
let export_exit_code = export(dir.clone(), ExportOpts { release: true })?;
131+
let export_exit_code = export(
132+
dir.clone(),
133+
ExportOpts {
134+
release: true,
135+
serve: false,
136+
host: String::new(),
137+
port: 0,
138+
},
139+
)?;
132140
if export_exit_code != 0 {
133141
return Ok(export_exit_code);
134142
}

packages/perseus-cli/src/export.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ pub fn export_internal(
181181
vec![&format!(
182182
"{} build --target web {}",
183183
env::var("PERSEUS_WASM_PACK_PATH").unwrap_or_else(|_| "wasm-pack".to_string()),
184-
if is_release { "--release" } else { "" }
184+
if is_release { "--release" } else { "--dev" }
185185
)],
186186
&wb_target,
187187
&wb_spinner,
@@ -197,8 +197,11 @@ pub fn export_internal(
197197
/// Builds the subcrates to get a directory that we can serve. Returns an exit code.
198198
pub fn export(dir: PathBuf, opts: ExportOpts) -> Result<i32, ExportError> {
199199
let spinners = MultiProgress::new();
200+
// We'll add another not-quite-spinner if we're serving
201+
let num_spinners = if opts.serve { 3 } else { 2 };
200202

201-
let (ep_thread, wb_thread) = export_internal(dir.clone(), &spinners, 2, opts.release)?;
203+
let (ep_thread, wb_thread) =
204+
export_internal(dir.clone(), &spinners, num_spinners, opts.release)?;
202205
let ep_res = ep_thread
203206
.join()
204207
.map_err(|_| ExecutionError::ThreadWaitFailed)??;

packages/perseus-cli/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ mod export;
3636
pub mod parse;
3737
mod prepare;
3838
mod serve;
39+
mod serve_exported;
3940
mod snoop;
4041
mod thread;
4142
mod tinker;
@@ -55,6 +56,7 @@ pub use eject::{eject, has_ejected};
5556
pub use export::export;
5657
pub use prepare::{check_env, prepare};
5758
pub use serve::serve;
59+
pub use serve_exported::serve_exported;
5860
pub use snoop::{snoop_build, snoop_server, snoop_wasm_build};
5961
pub use tinker::tinker;
6062

packages/perseus-cli/src/parse.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,18 @@ pub struct BuildOpts {
6666
pub release: bool,
6767
}
6868
/// Exports your app to purely static files
69-
#[derive(Parser)]
69+
#[derive(Parser, Clone)]
7070
pub struct ExportOpts {
7171
/// Export for production
7272
#[clap(long)]
7373
pub release: bool,
74+
/// Serve the generated static files locally
75+
#[clap(short, long)]
76+
pub serve: bool,
77+
#[clap(long, default_value = "127.0.0.1")]
78+
pub host: String,
79+
#[clap(long, default_value = "8080")]
80+
pub port: u16,
7481
}
7582
/// Serves your app (set the `$HOST` and `$PORT` environment variables to change the location it's served at)
7683
#[derive(Parser)]
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use console::Emoji;
2+
use std::net::SocketAddr;
3+
use std::path::PathBuf;
4+
use warp::Filter;
5+
6+
static SERVING: Emoji<'_, '_> = Emoji("🛰️ ", "");
7+
8+
/// Serves an exported app, assuming it's already been exported.
9+
pub async fn serve_exported(dir: PathBuf, host: String, port: u16) {
10+
let dir = dir.join(".perseus/dist/exported");
11+
// We actually don't have to worry about HTML file extensions at all
12+
let files = warp::any().and(warp::fs::dir(dir));
13+
// Parse `localhost` into `127.0.0.1` (picky Rust `std`)
14+
let host = if host == "localhost" {
15+
"127.0.0.1".to_string()
16+
} else {
17+
host
18+
};
19+
// Parse the host and port into an address
20+
let addr: SocketAddr = format!("{}:{}", host, port).parse().unwrap();
21+
// Notify the user that we're serving their files
22+
println!(
23+
" [3/3] {} Your exported app is now live at <http://{host}:{port}>!",
24+
SERVING,
25+
host = host,
26+
port = port
27+
);
28+
29+
warp::serve(files).run(addr).await
30+
}

0 commit comments

Comments
 (0)