use std::io::Write;
use std::path::PathBuf;
use std::fs::File;
use std::fs;
use std::str;
use std::env;
use std::error::Error as StdError;
use std::process;
use toml;
#[macro_use] extern crate clap;
use clap::App;
#[macro_use] extern crate lazy_static;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate serde_json;
#[macro_use] extern crate failure;
use failure::Error;
use failure::ResultExt;
use rusoto_core::Region;
use rusoto_cognito_idp::*;
mod prompt;
mod account;
mod config;
mod template;
mod cache;
mod builder;
mod components;
mod upload_client;
use config::*;
use template::load_templates;
use cache::FileCache;
use builder::AppBuilder;
use components::wasm::WasmComponent;
use components::pwa::PwaComponent;
use components::icon::IconComponent;
use components::splashscreen::SplashscreenComponent;
enum Command {
Init,
NewProject,
Setup,
Deploy,
Update,
Unknown,
}
impl From<&str> for Command {
fn from(s: &str) -> Command {
match s {
"init" => Command::Init,
"new" => Command::NewProject,
"setup" => Command::Setup,
"deploy" => Command::Deploy,
"update" => Command::Update,
_ => Command::Unknown
}
}
}
fn run() -> Result<(), Error> {
let handlebars = load_templates().context("Failed to load templates")?;
let yaml = load_yaml!("cli.yaml");
let app = App::from_yaml(yaml);
let input = app.get_matches();
let project_path = input.args.get("project")
.map_or(env::current_dir(),
|arg| Ok(PathBuf::from(&arg.vals[0])))
.context("Failed to get project path")?;
println!("Using project path {}", project_path.to_str().unwrap());
let home_path = input.args.get("home")
.map_or(default_home_path(),
|arg| Ok(PathBuf::from(&arg.vals[0])))
.context("Failed to get woz home path")?;
let encryption_key = FileCache::make_key(ENCRYPTION_PASSWORD, ENCRYPTION_SALT);
let cache = FileCache::new(encryption_key, home_path.clone());
println!("Using home path {}", home_path.to_str().unwrap());
if let Some(sub) = input.subcommand_name() {
match Command::from(sub) {
Command::Setup => {
fs::create_dir_all(&home_path).context("Failed to make home directory")?;
let values = prompt::signup();
let client = CognitoIdentityProviderClient::new(Region::UsWest2);
account::signup(client, values.email, values.username, values.password)
.sync()
.and_then(|resp| {
let user_id = resp.user_sub;
cache.set("user", user_id.as_bytes().to_vec())
.expect("Failed add user ID to cache");
Ok(())
})
.or_else(|e| {
println!("Signup failed {}", e.description());
Err(e)
})?;
println!("Please check your inbox for an email to verify your account.");
},
Command::NewProject => {
let subcommand_args = input.subcommand_matches("new").unwrap();
let project_name = subcommand_args.value_of("NAME").unwrap();
let command = format!("cargo new {} --lib", project_name);
process::Command::new("sh")
.arg("-c")
.arg(command)
.output()
.context("Failed to create new project using cargo")?;
let mut cargo_conf = fs::OpenOptions::new()
.append(true)
.open(PathBuf::from(format!("{}/Cargo.toml", project_name)))
.context("Failed to open Cargo.toml")?;
cargo_conf.write_all("seed = \"0.2.9\"
wasm-bindgen = \"0.2.40\"
web-sys = \"0.3.14\"
[lib]
crate-type = [\"cdylib\"]".as_bytes()).unwrap();
let mut woz_conf = File::create(
PathBuf::from(format!("{}/woz.toml", project_name))
).context("Failed to create woz config")?;
woz_conf.write_all(format!("name=\"Example: My App\"
project_id=\"{}\"
short_name=\"MyApp\"
lib=\"wasm-bindgen\"
wasm_path=\"target/wasm32-unknown-unknown/release/{}.wasm\"
", project_name, project_name).as_bytes()).unwrap();
let mut default_lib_rs = File::create(
PathBuf::from(format!("{}/src/lib.rs", project_name))
).context("Failed to create lib.rs")?;
default_lib_rs.write_all(DEFAULT_PROJECT_LIB_RS.as_bytes()).unwrap();
println!("New project created! Please cd to ./{}", project_name);
},
Command::Init => {
println!("Initializing current project directory...");
let cargo_conf = fs::read_to_string("./Config.toml")
.context("You must be in a cargo project")?
.parse::<toml::Value>()
.context("Failed to read Cargo.toml")?;
let project_name = &cargo_conf["package"]["name"];
let mut woz_conf = File::create(
PathBuf::from(format!("{}/woz.toml", project_name))
).context("Failed to create woz config")?;
woz_conf.write_all(format!("name=\"{}\"
project_id=\"{}\"
short_name=\"{}\"
lib=\"wasm-bindgen\"
wasm_path=\"target/wasm32-unknown-unknown/release/{}.wasm\"
", project_name, project_name, project_name, project_name).as_bytes()).unwrap();
println!("Ready to be deployed with 'woz deploy'");
},
Command::Deploy => {
println!("Deploying...");
let mut build_proc = process::Command::new("sh")
.arg("-c")
.arg("cargo build --release --target wasm32-unknown-unknown")
.stdout(process::Stdio::piped())
.spawn()
.context("Failed to spawn build")?;
let exit_code = build_proc.wait().context("Failed to wait for build")?;
if !exit_code.success() {
return Err(format_err!("Build failed, please check output above."))
}
let mut conf_path = project_path.clone();
conf_path.push("woz.toml");
let conf_str = fs::read_to_string(conf_path.clone())
.context(format!("Couldn't find woz config file at {}",
conf_path.clone().to_str().unwrap()))?;
let conf: Config = toml::from_str(&conf_str)
.context("Failed to parse woz config")?;
let s3_client = upload_client::authenticated_client(&cache)
.context("Unable to initialize upload client")?;
let identity_id = cache.get("identity")
.context("Unable to retrieve user ID")?;
let ProjectId(project_id) = conf.project_id.clone();
let mut out_path = home_path.clone();
out_path.push(&project_id);
out_path.push("pkg");
fs::create_dir_all(&out_path).context("Failed to make pkg directory")?;
let key_prefix = format!("{}/{}", &identity_id, &project_id);
let mut wasm_path = project_path.clone();
wasm_path.push(conf.wasm_path.clone());
let wasm_cmpnt = WasmComponent::new(wasm_path, out_path);
let pwa_cmpnt = PwaComponent::new(&conf, handlebars);
let icon_cmpnt = IconComponent::new(&conf);
let splashscreen_cmpnt = SplashscreenComponent::new(&conf);
let mut app = AppBuilder::new(s3_client, key_prefix);
app
.component(wasm_cmpnt)
.component(pwa_cmpnt)
.component(icon_cmpnt)
.component(splashscreen_cmpnt);
app.upload().context("Failed to upload app")?;
let location = format!(
"{}://{}/{}/{}/index.html",
SCHEME,
NETLOC,
identity_id,
project_id
);
println!("{}", format!("Your app is available at {}", location));
}
_ => unimplemented!()
};
};
Ok(())
}
fn main() {
run().map_err(|e| println!("{}", e)).ok();
}