Skip to content

Commit

Permalink
feat: ✨ added build command to cli
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Aug 29, 2021
1 parent 9e5afd1 commit 66dc282
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 16 deletions.
3 changes: 3 additions & 0 deletions packages/perseus-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ edition = "2018"
include_dir = "0.6"
error-chain = "0.12"
cargo_toml = "0.9"
indicatif = "0.16"
console = "0.14"
notify = "4.0"

[lib]
name = "lib"
Expand Down
18 changes: 9 additions & 9 deletions packages/perseus-cli/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use lib::errors::*;

// All this does is run the program and terminate with the acquired exit code
fn main() {
// In development, we'll test in the basic example
// In development, we'll test in the `basic` example
if cfg!(debug_assertions) {
env::set_current_dir("../../examples/basic").unwrap();
env::set_current_dir("../../examples/cli").unwrap();
}
let exit_code = real_main();
std::process::exit(exit_code)
Expand Down Expand Up @@ -60,7 +60,7 @@ fn core(dir: PathBuf) -> Result<i32> {
// Check for special arguments
if matches!(prog_args.get(0), Some(_)) {
if prog_args[0] == "-v" || prog_args[0] == "--version" {
writeln!(stdout, "You are currently running the Perseus CLI v{}! You can see the latest release at https://github.com/arctic-hen7/perseus/releases.", PERSEUS_VERSION).expect("Failed to write version.");
writeln!(stdout, "You are currently running the Perseus CLI v{}! You can see the latest release at https://github.com/arctic-hen7/perseus/releases.", PERSEUS_VERSION).expect("Failed to write to stdout.");
Ok(0)
} else if prog_args[0] == "-h" || prog_args[0] == "--help" {
help(stdout);
Expand All @@ -70,24 +70,24 @@ fn core(dir: PathBuf) -> Result<i32> {
if prog_args[0] == "build" {
// Set up the '.perseus/' directory if needed
prepare(dir.clone())?;
build(dir, &prog_args)?;
Ok(0)
let exit_code = build(dir, &prog_args)?;
Ok(exit_code)
} else if prog_args[0] == "serve" {
// Set up the '.perseus/' directory if needed
prepare(dir.clone())?;
serve(dir, &prog_args)?;
Ok(0)
let exit_code = serve(dir, &prog_args)?;
Ok(exit_code)
} else if prog_args[0] == "clean" {
// Just delete the '.perseus/' directory directly, as we'd do in a corruption
delete_bad_dir(dir)?;
Ok(0)
} else {
writeln!(stdout, "Unknown command '{}'. You can see the help page with -h/--help.", prog_args[0]);
writeln!(stdout, "Unknown command '{}'. You can see the help page with -h/--help.", prog_args[0]).expect("Failed to write to stdout.");
Ok(1)
}
}
} else {
writeln!(stdout, "Please provide a command to run, or use -h/--help to see the help page.");
writeln!(stdout, "Please provide a command to run, or use -h/--help to see the help page.").expect("Failed to write to stdout.");
Ok(1)
}
}
81 changes: 78 additions & 3 deletions packages/perseus-cli/src/build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,82 @@
use std::path::PathBuf;
use console::{style, Emoji};
use crate::cmd::run_stage;
use crate::errors::*;

/// Builds the subcrates to get a directory that we can serve.
pub fn build(dir: PathBuf, prog_args: &[String]) -> Result<()> {
todo!("build command")
// Emojis for stages
static GENERATING: Emoji<'_, '_> = Emoji("🔨", "");
static BUILDING: Emoji<'_, '_> = Emoji("🏗️ ", ""); // Yes, there's a space here, for some reason it's needed...
static FINALIZING: Emoji<'_, '_> = Emoji("📦", "");

/// Returns the exit code if it's non-zero.
macro_rules! handle_exit_code {
($code:expr) => {
let code = $code;
if code != 0 {
return Ok(code);
}
};
}

/// Actually builds the user's code, program arguments having been interpreted.
fn build_internal(dir: PathBuf) -> Result<i32> {
let mut target = dir;
target.extend([".perseus"]);

// Static generation
handle_exit_code!(run_stage(
vec![
"cargo run"
],
&target,
format!(
"{} {} Generating your app",
style("[1/3]").bold().dim(),
GENERATING
)
)?);
// WASM building
handle_exit_code!(run_stage(
vec![
"wasm-pack build --target web",
// Move the `pkg/` directory into `dist/pkg/`
"rm -rf dist/pkg",
"mv pkg/ dist/",
],
&target,
format!(
"{} {} Building your app to WASM",
style("[2/3]").bold().dim(),
BUILDING
)
)?);
// JS bundle generation
handle_exit_code!(run_stage(
vec![
"rollup main.js --format iife --file dist/pkg/bundle.js"
],
&target,
format!(
"{} {} Finalizing bundle",
style("[3/3]").bold().dim(),
FINALIZING
)
)?);

Ok(0)
}

/// Builds the subcrates to get a directory that we can serve. Returns an exit code.
pub fn build(dir: PathBuf, prog_args: &[String]) -> Result<i32> {
// TODO support watching files
// If we should watch for file changes, do so
let should_watch = prog_args.get(1);
let dflt_watch_path = ".".to_string();
let _watch_path = prog_args.get(2).unwrap_or(&dflt_watch_path);
if should_watch == Some(&"-w".to_string()) || should_watch == Some(&"--watch".to_string()) {
todo!("watching not yet supported, try a tool like 'entr'");
}
let exit_code = build_internal(dir.clone())?;

Ok(exit_code)
}
75 changes: 75 additions & 0 deletions packages/perseus-cli/src/cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use std::process::Command;
use std::path::Path;
use std::io::Write;
use indicatif::{ProgressBar, ProgressStyle};
use console::Emoji;
use crate::errors::*;

// Some useful emojis
static SUCCESS: Emoji<'_, '_> = Emoji("✅", "success!");
static FAILURE: Emoji<'_, '_> = Emoji("❌", "failed!");

/// Runs the given command conveniently, returning the exit code. Notably, this parses the given command by separating it on spaces.
pub fn run_cmd(raw_cmd: String, dir: &Path, pre_dump: impl Fn()) -> Result<i32> {
let mut cmd_args: Vec<&str> = raw_cmd.split(' ').collect();
let cmd = cmd_args.remove(0);

// This will NOT pipe output/errors to the console
let output = Command::new(&cmd)
.args(cmd_args)
.current_dir(dir)
.output()
.map_err(|err| ErrorKind::CmdExecFailed(raw_cmd.clone(), err.to_string()))?;

let exit_code = match output.status.code() {
Some(exit_code) => exit_code, // If we have an exit code, use it
None if output.status.success() => 0, // If we don't, but we know the command succeeded, return 0 (success code)
None => 1, // If we don't know an exit code but we know that the command failed, return 1 (general error code)
};

// Print `stderr` only if there's something therein and the exit code is non-zero
if !output.stderr.is_empty() && exit_code != 0 {
pre_dump();
std::io::stderr().write_all(&output.stderr).unwrap();
}

Ok(exit_code)
}

pub fn run_stage(cmds: Vec<&str>, target: &Path, message: String) -> Result<i32> {
// Tell the user about the stage with a nice progress bar
let spinner = ProgressBar::new_spinner();
spinner.set_style(
ProgressStyle::default_spinner()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ")
);
spinner.set_message(format!("{}...", message));
// Tick the spinner every 50 milliseconds
spinner.enable_steady_tick(50);

// Run the commands
for cmd in cmds {
// We make sure all commands run in the target directory ('.perseus/' itself)
let exit_code = run_cmd(cmd.to_string(), target, || {
// We're done, we'll write a more permanent version of the message
spinner.finish_with_message(format!(
"{}...{}",
message,
FAILURE
))
})?;
// If we have a non-zero exit code, we should NOT continue (stderr has been written to the console already)
if exit_code != 0 {
return Ok(1);
}
}

// We're done, we'll write a more permanent version of the message
spinner.finish_with_message(format!(
"{}...{}",
message,
SUCCESS
));

Ok(0)
}
10 changes: 10 additions & 0 deletions packages/perseus-cli/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ error_chain! {
display("Couldn't remove '.perseus/' directory at '{:?}'. Please remove the '.perseus/' directory manually (particularly if you didn't intentionally run the 'clean' command, that means the directory has been corrupted). Error was: '{}'.", target, err)

}
/// For when executing a system command after preparation failed. This shouldn't cause a directory deletion.
CmdExecFailed(cmd: String, err: String) {
description("command exeuction failed")
display("Couldn't execute command '{}'. Error was: '{}'.", cmd, err)
}
/// For when watching failes for changes failed.
WatcherFailed(path: String, err: String) {
description("watching files failed")
display("Couldn't watch '{}' for changes. Error was: '{}'.", path, err)
}
}
}

Expand Down
7 changes: 4 additions & 3 deletions packages/perseus-cli/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ This is the CLI for Perseus, a super-fast WebAssembly frontend development frame
-h, --help prints this help page
-v, --version prints the current version of the CLI
build builds your app (-p/--prod for production, -w/--watch to watch files)
serve serves your app (accepts $PORT and $HOST env vars)
build builds your app
serve serves your app (accepts $PORT and $HOST env vars, --no-build to serve pre-built files)
Please note that watching for file changes is not yet inbuilt, but can be achieved with a tool like 'entr' in the meantime.
Further information can be found at https://arctic-hen7.github.io/perseus.
",
version = PERSEUS_VERSION
)
.expect("Failed to write help page.")
}
}
1 change: 1 addition & 0 deletions packages/perseus-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod prepare;
pub mod errors;
mod build;
mod serve;
mod cmd;

use errors::*;
use std::fs;
Expand Down
2 changes: 1 addition & 1 deletion packages/perseus-cli/src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ use crate::errors::*;
/// Serves the user's app. If no arguments are provided, this will build in watch mode and serve. If `-p/--prod` is specified, we'll
/// build for development, and if `--no-build` is specified, we won't build at all (useful for pseudo-production serving).
/// General message though: do NOT use the CLI for production serving!
pub fn serve(dir: PathBuf, prog_args: &[String]) -> Result<()> {
pub fn serve(dir: PathBuf, prog_args: &[String]) -> Result<i32> {
todo!("serve command")
}

0 comments on commit 66dc282

Please sign in to comment.