diff --git a/src/bindgen.rs b/src/bindgen.rs index 3796b47..7405dde 100644 --- a/src/bindgen.rs +++ b/src/bindgen.rs @@ -6,17 +6,16 @@ use PBAR; pub fn cargo_install_wasm_bindgen(step: &Step) -> Result<(), Error> { let msg = format!("{}Installing WASM-bindgen...", emoji::DOWN_ARROW); - let pb = PBAR.step(step, &msg); + PBAR.step(step, &msg)?; let output = Command::new("cargo") .arg("install") .arg("wasm-bindgen-cli") .arg("--force") .output()?; - pb.finish(); if !output.status.success() { let s = String::from_utf8_lossy(&output.stderr); if s.contains("already exists") { - PBAR.info("wasm-bindgen already installed"); + PBAR.info("wasm-bindgen already installed")?; return Ok(()); } Error::cli("Installing wasm-bindgen failed", s) @@ -34,7 +33,7 @@ pub fn wasm_bindgen_build( step: &Step, ) -> Result<(), Error> { let msg = format!("{}Running WASM-bindgen...", emoji::RUNNER); - let pb = PBAR.step(step, &msg); + PBAR.step(step, &msg)?; let binary_name = name.replace("-", "_"); let release_or_debug = if debug { "debug" } else { "release" }; let wasm_path = format!( @@ -60,7 +59,6 @@ pub fn wasm_bindgen_build( .arg(dts_arg) .arg(target_arg) .output()?; - pb.finish(); if !output.status.success() { let s = String::from_utf8_lossy(&output.stderr); Error::cli("wasm-bindgen failed to execute properly", s) diff --git a/src/build.rs b/src/build.rs index e85e123..9ece1ab 100644 --- a/src/build.rs +++ b/src/build.rs @@ -6,7 +6,7 @@ use PBAR; pub fn rustup_add_wasm_target(step: &Step) -> Result<(), Error> { let msg = format!("{}Adding WASM target...", emoji::TARGET); - let pb = PBAR.step(step, &msg); + PBAR.step(step, &msg)?; ensure_nightly()?; let output = Command::new("rustup") .arg("target") @@ -15,7 +15,6 @@ pub fn rustup_add_wasm_target(step: &Step) -> Result<(), Error> { .arg("--toolchain") .arg("nightly") .output()?; - pb.finish(); if !output.status.success() { let s = String::from_utf8_lossy(&output.stderr); Error::cli("Adding the wasm32-unknown-unknown target failed", s) @@ -42,7 +41,7 @@ fn ensure_nightly() -> Result<(), Error> { pub fn cargo_build_wasm(path: &str, debug: bool, step: &Step) -> Result<(), Error> { let msg = format!("{}Compiling to WASM...", emoji::CYCLONE); - let pb = PBAR.step(step, &msg); + PBAR.step(step, &msg)?; let output = { let mut cmd = Command::new("cargo"); cmd.current_dir(path).arg("+nightly").arg("build"); @@ -52,7 +51,7 @@ pub fn cargo_build_wasm(path: &str, debug: bool, step: &Step) -> Result<(), Erro cmd.arg("--target").arg("wasm32-unknown-unknown"); cmd.output()? }; - pb.finish(); + if !output.status.success() { let s = String::from_utf8_lossy(&output.stderr); Error::cli("Compilation of your program failed", s) diff --git a/src/command.rs b/src/command.rs deleted file mode 100644 index 310d36c..0000000 --- a/src/command.rs +++ /dev/null @@ -1,516 +0,0 @@ -use bindgen; -use build; -use emoji; -use error::Error; -use indicatif::HumanDuration; -use manifest; -use npm; -use progressbar::Step; -#[allow(unused)] -use readme; -use slog::Logger; -use std::fs; -use std::result; -use std::time::Instant; -use PBAR; - -#[derive(Debug, StructOpt)] -pub enum Command { - #[structopt(name = "init")] - /// 🐣 initialize a package.json based on your compiled wasm! - Init { - path: Option, - - #[structopt(long = "scope", short = "s")] - scope: Option, - - #[structopt(long = "--skip-build")] - /// Do not build, only update metadata - skip_build: bool, - - #[structopt(long = "no-typescript")] - /// By default a *.d.ts file is generated for the generated JS file, but - /// this flag will disable generating this TypeScript file. - disable_dts: bool, - - #[structopt(long = "target", short = "t", default_value = "browser")] - /// Sets the target environment. [possible values: browser, nodejs] - target: String, - - #[structopt(long = "debug")] - /// Build without --release. - debug: bool, - }, - - #[structopt(name = "pack")] - /// 🍱 create a tar of your npm package but don't publish! - Pack { path: Option }, - - #[structopt(name = "publish")] - /// 🎆 pack up your npm package and publish! - Publish { path: Option }, - - #[structopt(name = "login", alias = "adduser", alias = "add-user")] - /// 👤 Add a registry user account! (aliases: adduser, add-user) - Login { - #[structopt(long = "registry", short = "r")] - /// Default: 'https://registry.npmjs.org/'. - /// The base URL of the npm package registry. If scope is also - /// specified, this registry will only be used for packages with that - /// scope. scope defaults to the scope of the project directory you're - /// currently in, if any. - registry: Option, - - #[structopt(long = "scope", short = "s")] - /// Default: none. - /// If specified, the user and login credentials given will be - /// associated with the specified scope. - scope: Option, - - #[structopt(long = "always-auth", short = "a")] - /// If specified, save configuration indicating that all requests to the - /// given registry should include authorization information. Useful for - /// private registries. Can be used with --registry and / or --scope - always_auth: bool, - - #[structopt(long = "auth-type", short = "t")] - /// Default: 'legacy'. - /// Type: 'legacy', 'sso', 'saml', 'oauth'. - /// What authentication strategy to use with adduser/login. Some npm - /// registries (for example, npmE) might support alternative auth - /// strategies besides classic username/password entry in legacy npm. - auth_type: Option, - }, -} - -pub fn run_wasm_pack(command: Command, log: &Logger) -> result::Result<(), Error> { - // Run the correct command based off input and store the result of it so that we can clear - // the progress bar then return it - let status = match command { - Command::Init { - path, - scope, - skip_build, - disable_dts, - target, - debug, - } => { - info!(&log, "Running init command..."); - info!( - &log, - "Path: {:?}, Scope: {:?}, Skip build: {}, Disable Dts: {}, Target: {}, Debug: {}", - &path, - &scope, - &skip_build, - &disable_dts, - &target, - debug - ); - let mode = if skip_build { - InitMode::Nobuild - } else { - InitMode::Normal - }; - Init::new(path, scope, disable_dts, target, debug).process(&log, mode) - } - Command::Pack { path } => { - info!(&log, "Running pack command..."); - info!(&log, "Path: {:?}", &path); - pack(path, &log) - } - Command::Publish { path } => { - info!(&log, "Running publish command..."); - info!(&log, "Path: {:?}", &path); - publish(path, &log) - } - Command::Login { - registry, - scope, - always_auth, - auth_type, - } => { - info!(&log, "Running login command..."); - info!( - &log, - "Registry: {:?}, Scope: {:?}, Always Auth: {}, Auth Type: {:?}", - ®istry, - &scope, - &always_auth, - &auth_type - ); - login(registry, scope, always_auth, auth_type, &log) - } - }; - - match status { - Ok(_) => {} - Err(ref e) => { - error!(&log, "{}", e); - PBAR.error(e.error_type()); - } - } - - // Make sure we always clear the progress bar before we abort the program otherwise - // stderr and stdout output get eaten up and nothing will work. If this part fails - // to work and clear the progress bars then you're really having a bad day with your tools. - PBAR.done()?; - - // Return the actual status of the program to the main function - status -} - -// quicli::prelude::* imports a different result struct which gets -// precedence over the std::result::Result, so have had to specify -// the correct type here. -pub fn create_pkg_dir(path: &str, step: &Step) -> result::Result<(), Error> { - let msg = format!("{}Creating a pkg directory...", emoji::FOLDER); - let pb = PBAR.step(step, &msg); - let pkg_dir_path = format!("{}/pkg", path); - fs::create_dir_all(pkg_dir_path)?; - pb.finish(); - Ok(()) -} - -enum InitMode { - Normal, - Nobuild, -} - -struct Init { - crate_path: String, - scope: Option, - disable_dts: bool, - target: String, - debug: bool, - crate_name: Option, -} - -type InitStep = fn(&mut Init, &Step, &Logger) -> result::Result<(), Error>; - -impl Init { - pub fn new( - path: Option, - scope: Option, - disable_dts: bool, - target: String, - debug: bool, - ) -> Init { - Init { - crate_path: set_crate_path(path), - scope, - disable_dts, - target, - debug, - crate_name: None, - } - } - - fn get_process_steps(mode: InitMode) -> Vec<(&'static str, InitStep)> { - macro_rules! steps { - ($($name:ident),+) => { - { - let mut steps: Vec<(&'static str, InitStep)> = Vec::new(); - $(steps.push((stringify!($name), Init::$name));)* - steps - } - }; - ($($name:ident,)*) => (steps![$($name),*]) - } - - match mode { - InitMode::Normal => steps![ - step_check_dependency, - step_add_wasm_target, - step_build_wasm, - step_create_dir, - step_create_json, - step_copy_readme, - step_check_create_type, - step_install_wasm_bindgen, - step_running_wasm_bindgen, - ], - InitMode::Nobuild => steps![ - step_check_dependency, - step_create_dir, - step_create_json, - step_copy_readme, - ], - } - } - - pub fn process(&mut self, log: &Logger, mode: InitMode) -> result::Result<(), Error> { - let process_steps = Init::get_process_steps(mode); - - let mut step_counter = Step::new(process_steps.len()); - - let started = Instant::now(); - - for (_, process_step) in process_steps { - process_step(self, &step_counter, log)?; - step_counter.inc(); - } - - let duration = HumanDuration(started.elapsed()); - info!(&log, "Done in {}.", &duration); - info!( - &log, - "Your WASM pkg is ready to publish at {}/pkg.", &self.crate_path - ); - - PBAR.message(&format!("{} Done in {}", emoji::SPARKLE, &duration)); - - PBAR.message(&format!( - "{} Your WASM pkg is ready to publish at {}/pkg.", - emoji::PACKAGE, - &self.crate_path - )); - Ok(()) - } - - fn step_check_dependency(&mut self, _step: &Step, log: &Logger) -> result::Result<(), Error> { - info!(&log, "Checking wasm-bindgen dependency..."); - manifest::check_wasm_bindgen(&self.crate_path)?; - info!(&log, "wasm-bindgen dependency is correctly declared."); - Ok(()) - } - - fn step_add_wasm_target(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { - info!(&log, "Adding wasm-target..."); - build::rustup_add_wasm_target(step)?; - info!(&log, "Adding wasm-target was successful."); - Ok(()) - } - - fn step_build_wasm(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { - info!(&log, "Building wasm..."); - build::cargo_build_wasm(&self.crate_path, self.debug, step)?; - - #[cfg(not(target_os = "windows"))] - info!( - &log, - "wasm built at {}/target/wasm32-unknown-unknown/release.", &self.crate_path - ); - #[cfg(target_os = "windows")] - info!( - &log, - "wasm built at {}\\target\\wasm32-unknown-unknown\\release.", &self.crate_path - ); - Ok(()) - } - - fn step_create_dir(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { - info!(&log, "Creating a pkg directory..."); - create_pkg_dir(&self.crate_path, step)?; - info!(&log, "Created a pkg directory at {}.", &self.crate_path); - Ok(()) - } - - fn step_create_json(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { - info!(&log, "Writing a package.json..."); - manifest::write_package_json(&self.crate_path, &self.scope, self.disable_dts, step)?; - #[cfg(not(target_os = "windows"))] - info!( - &log, - "Wrote a package.json at {}/pkg/package.json.", &self.crate_path - ); - #[cfg(target_os = "windows")] - info!( - &log, - "Wrote a package.json at {}\\pkg\\package.json.", &self.crate_path - ); - Ok(()) - } - - fn step_copy_readme(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { - info!(&log, "Copying readme from crate..."); - readme::copy_from_crate(&self.crate_path, step)?; - #[cfg(not(target_os = "windows"))] - info!( - &log, - "Copied readme from crate to {}/pkg.", &self.crate_path - ); - #[cfg(target_os = "windows")] - info!( - &log, - "Copied readme from crate to {}\\pkg.", &self.crate_path - ); - Ok(()) - } - - fn step_check_create_type(&mut self, _step: &Step, log: &Logger) -> result::Result<(), Error> { - info!(&log, "Checking the crate type from the manifest..."); - manifest::check_crate_type(&self.crate_path)?; - #[cfg(not(target_os = "windows"))] - info!( - &log, - "Checked crate type from the manifest at {}/Cargo.toml.", &self.crate_path - ); - #[cfg(target_os = "windows")] - info!( - &log, - "Checked crate type from the manifest at {}\\Cargo.toml.", &self.crate_path - ); - - Ok(()) - } - - fn step_install_wasm_bindgen( - &mut self, - step: &Step, - log: &Logger, - ) -> result::Result<(), Error> { - info!(&log, "Installing wasm-bindgen-cli..."); - bindgen::cargo_install_wasm_bindgen(step)?; - info!(&log, "Installing wasm-bindgen-cli was successful."); - - info!(&log, "Getting the crate name from the manifest..."); - self.crate_name = Some(manifest::get_crate_name(&self.crate_path)?); - #[cfg(not(target_os = "windows"))] - info!( - &log, - "Got crate name {} from the manifest at {}/Cargo.toml.", - &self.crate_name.as_ref().unwrap(), - &self.crate_path - ); - #[cfg(target_os = "windows")] - info!( - &log, - "Got crate name {} from the manifest at {}\\Cargo.toml.", - &self.crate_name.as_ref().unwrap(), - &self.crate_path - ); - Ok(()) - } - - fn step_running_wasm_bindgen( - &mut self, - step: &Step, - log: &Logger, - ) -> result::Result<(), Error> { - info!(&log, "Building the wasm bindings..."); - bindgen::wasm_bindgen_build( - &self.crate_path, - &self.crate_name.as_ref().unwrap(), - self.disable_dts, - &self.target, - self.debug, - step, - )?; - #[cfg(not(target_os = "windows"))] - info!( - &log, - "wasm bindings were built at {}/pkg.", &self.crate_path - ); - #[cfg(target_os = "windows")] - info!( - &log, - "wasm bindings were built at {}\\pkg.", &self.crate_path - ); - Ok(()) - } -} - -fn pack(path: Option, log: &Logger) -> result::Result<(), Error> { - let crate_path = set_crate_path(path); - - info!(&log, "Packing up the npm package..."); - npm::npm_pack(&crate_path)?; - #[cfg(not(target_os = "windows"))] - info!(&log, "Your package is located at {}/pkg", &crate_path); - #[cfg(target_os = "windows")] - info!(&log, "Your package is located at {}\\pkg", &crate_path); - - PBAR.message("🎒 packed up your package!"); - Ok(()) -} - -fn publish(path: Option, log: &Logger) -> result::Result<(), Error> { - let crate_path = set_crate_path(path); - - info!(&log, "Publishing the npm package..."); - info!(&log, "npm info located in the npm debug log"); - npm::npm_publish(&crate_path)?; - info!(&log, "Published your package!"); - - PBAR.message("💥 published your package!"); - Ok(()) -} - -fn login( - registry: Option, - scope: Option, - always_auth: bool, - auth_type: Option, - log: &Logger, -) -> result::Result<(), Error> { - let registry = registry.unwrap_or(npm::DEFAULT_NPM_REGISTRY.to_string()); - - info!(&log, "Logging in to npm..."); - info!( - &log, - "Scope: {:?} Registry: {}, Always Auth: {}, Auth Type: {:?}.", - &scope, - ®istry, - always_auth, - &auth_type - ); - info!(&log, "npm info located in the npm debug log"); - npm::npm_login(®istry, &scope, always_auth, &auth_type)?; - info!(&log, "Logged you in!"); - - PBAR.message(&format!("👋 logged you in!")); - Ok(()) -} - -fn set_crate_path(path: Option) -> String { - let crate_path = match path { - Some(p) => p, - None => ".".to_string(), - }; - - crate_path -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn init_normal_build() { - let steps: Vec<&str> = Init::get_process_steps(InitMode::Normal) - .into_iter() - .map(|(n, _)| n) - .collect(); - assert_eq!( - steps, - [ - "step_check_dependency", - "step_add_wasm_target", - "step_build_wasm", - "step_create_dir", - "step_create_json", - "step_copy_readme", - "step_check_create_type", - "step_install_wasm_bindgen", - "step_running_wasm_bindgen" - ] - ); - } - - #[test] - fn init_skip_build() { - let steps: Vec<&str> = Init::get_process_steps(InitMode::Nobuild) - .into_iter() - .map(|(n, _)| n) - .collect(); - assert_eq!( - steps, - [ - "step_check_dependency", - "step_create_dir", - "step_create_json", - "step_copy_readme" - ] - ); - } -} diff --git a/src/command/init.rs b/src/command/init.rs new file mode 100644 index 0000000..c409a86 --- /dev/null +++ b/src/command/init.rs @@ -0,0 +1,278 @@ +use bindgen; +use build; +use command::utils::set_crate_path; +use emoji; +use error::Error; +use indicatif::HumanDuration; +use manifest; +use progressbar::Step; +use readme; +use slog::Logger; +use std::time::Instant; +use std::{fs, result}; +use PBAR; + +// quicli::prelude::* imports a different result struct which gets +// precedence over the std::result::Result, so have had to specify +// the correct type here. +pub fn create_pkg_dir(path: &str, step: &Step) -> result::Result<(), Error> { + let msg = format!("{}Creating a pkg directory...", emoji::FOLDER); + PBAR.step(step, &msg)?; + let pkg_dir_path = format!("{}/pkg", path); + fs::create_dir_all(pkg_dir_path)?; + Ok(()) +} + +pub enum InitMode { + Normal, + Nobuild, +} + +pub struct Init { + crate_path: String, + scope: Option, + disable_dts: bool, + target: String, + debug: bool, + crate_name: Option, +} + +type InitStep = fn(&mut Init, &Step, &Logger) -> result::Result<(), Error>; + +impl Init { + pub fn new( + path: Option, + scope: Option, + disable_dts: bool, + target: String, + debug: bool, + ) -> Init { + Init { + crate_path: set_crate_path(path), + scope, + disable_dts, + target, + debug, + crate_name: None, + } + } + + fn get_process_steps(mode: InitMode) -> Vec<(&'static str, InitStep)> { + macro_rules! steps { + ($($name:ident),+) => { + { + let mut steps: Vec<(&'static str, InitStep)> = Vec::new(); + $(steps.push((stringify!($name), Init::$name));)* + steps + } + }; + ($($name:ident,)*) => (steps![$($name),*]) + } + + match mode { + InitMode::Normal => steps![ + step_check_crate_config, + step_add_wasm_target, + step_build_wasm, + step_create_dir, + step_create_json, + step_copy_readme, + step_install_wasm_bindgen, + step_running_wasm_bindgen, + ], + InitMode::Nobuild => steps![step_create_dir, step_create_json, step_copy_readme,], + } + } + + pub fn process(&mut self, log: &Logger, mode: InitMode) -> result::Result<(), Error> { + let process_steps = Init::get_process_steps(mode); + + let mut step_counter = Step::new(process_steps.len()); + + let started = Instant::now(); + + for (_, process_step) in process_steps { + process_step(self, &step_counter, log)?; + step_counter.inc(); + } + + let duration = HumanDuration(started.elapsed()); + info!(&log, "Done in {}.", &duration); + info!( + &log, + "Your WASM pkg is ready to publish at {}/pkg.", &self.crate_path + ); + + PBAR.message(&format!("{} Done in {}", emoji::SPARKLE, &duration))?; + + PBAR.message(&format!( + "{} Your WASM pkg is ready to publish at {}/pkg.", + emoji::PACKAGE, + &self.crate_path + ))?; + Ok(()) + } + + fn step_check_crate_config(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { + info!(&log, "Checking crate configuration..."); + manifest::check_crate_config(&self.crate_path, step)?; + info!(&log, "Crate is correctly configured."); + Ok(()) + } + + fn step_add_wasm_target(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { + info!(&log, "Adding wasm-target..."); + build::rustup_add_wasm_target(step)?; + info!(&log, "Adding wasm-target was successful."); + Ok(()) + } + + fn step_build_wasm(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { + info!(&log, "Building wasm..."); + build::cargo_build_wasm(&self.crate_path, self.debug, step)?; + + #[cfg(not(target_os = "windows"))] + info!( + &log, + "wasm built at {}/target/wasm32-unknown-unknown/release.", &self.crate_path + ); + #[cfg(target_os = "windows")] + info!( + &log, + "wasm built at {}\\target\\wasm32-unknown-unknown\\release.", &self.crate_path + ); + Ok(()) + } + + fn step_create_dir(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { + info!(&log, "Creating a pkg directory..."); + create_pkg_dir(&self.crate_path, step)?; + info!(&log, "Created a pkg directory at {}.", &self.crate_path); + Ok(()) + } + + fn step_create_json(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { + info!(&log, "Writing a package.json..."); + manifest::write_package_json(&self.crate_path, &self.scope, self.disable_dts, step)?; + #[cfg(not(target_os = "windows"))] + info!( + &log, + "Wrote a package.json at {}/pkg/package.json.", &self.crate_path + ); + #[cfg(target_os = "windows")] + info!( + &log, + "Wrote a package.json at {}\\pkg\\package.json.", &self.crate_path + ); + Ok(()) + } + + fn step_copy_readme(&mut self, step: &Step, log: &Logger) -> result::Result<(), Error> { + info!(&log, "Copying readme from crate..."); + readme::copy_from_crate(&self.crate_path, step)?; + #[cfg(not(target_os = "windows"))] + info!( + &log, + "Copied readme from crate to {}/pkg.", &self.crate_path + ); + #[cfg(target_os = "windows")] + info!( + &log, + "Copied readme from crate to {}\\pkg.", &self.crate_path + ); + Ok(()) + } + + fn step_install_wasm_bindgen( + &mut self, + step: &Step, + log: &Logger, + ) -> result::Result<(), Error> { + info!(&log, "Installing wasm-bindgen-cli..."); + bindgen::cargo_install_wasm_bindgen(step)?; + info!(&log, "Installing wasm-bindgen-cli was successful."); + + info!(&log, "Getting the crate name from the manifest..."); + self.crate_name = Some(manifest::get_crate_name(&self.crate_path)?); + #[cfg(not(target_os = "windows"))] + info!( + &log, + "Got crate name {} from the manifest at {}/Cargo.toml.", + &self.crate_name.as_ref().unwrap(), + &self.crate_path + ); + #[cfg(target_os = "windows")] + info!( + &log, + "Got crate name {} from the manifest at {}\\Cargo.toml.", + &self.crate_name.as_ref().unwrap(), + &self.crate_path + ); + Ok(()) + } + + fn step_running_wasm_bindgen( + &mut self, + step: &Step, + log: &Logger, + ) -> result::Result<(), Error> { + info!(&log, "Building the wasm bindings..."); + bindgen::wasm_bindgen_build( + &self.crate_path, + &self.crate_name.as_ref().unwrap(), + self.disable_dts, + &self.target, + self.debug, + step, + )?; + #[cfg(not(target_os = "windows"))] + info!( + &log, + "wasm bindings were built at {}/pkg.", &self.crate_path + ); + #[cfg(target_os = "windows")] + info!( + &log, + "wasm bindings were built at {}\\pkg.", &self.crate_path + ); + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn init_normal_build() { + let steps: Vec<&str> = Init::get_process_steps(InitMode::Normal) + .into_iter() + .map(|(n, _)| n) + .collect(); + assert_eq!( + steps, + [ + "step_check_crate_config", + "step_add_wasm_target", + "step_build_wasm", + "step_create_dir", + "step_create_json", + "step_copy_readme", + "step_install_wasm_bindgen", + "step_running_wasm_bindgen" + ] + ); + } + + #[test] + fn init_skip_build() { + let steps: Vec<&str> = Init::get_process_steps(InitMode::Nobuild) + .into_iter() + .map(|(n, _)| n) + .collect(); + assert_eq!( + steps, + ["step_create_dir", "step_create_json", "step_copy_readme"] + ); + } +} diff --git a/src/command/login.rs b/src/command/login.rs new file mode 100644 index 0000000..f28e1ec --- /dev/null +++ b/src/command/login.rs @@ -0,0 +1,31 @@ +use error::Error; +use npm; +use slog::Logger; +use std::result; +use PBAR; + +pub fn login( + registry: Option, + scope: Option, + always_auth: bool, + auth_type: Option, + log: &Logger, +) -> result::Result<(), Error> { + let registry = registry.unwrap_or(npm::DEFAULT_NPM_REGISTRY.to_string()); + + info!(&log, "Logging in to npm..."); + info!( + &log, + "Scope: {:?} Registry: {}, Always Auth: {}, Auth Type: {:?}.", + &scope, + ®istry, + always_auth, + &auth_type + ); + info!(&log, "npm info located in the npm debug log"); + npm::npm_login(®istry, &scope, always_auth, &auth_type)?; + info!(&log, "Logged you in!"); + + PBAR.message(&format!("👋 logged you in!"))?; + Ok(()) +} diff --git a/src/command/mod.rs b/src/command/mod.rs new file mode 100644 index 0000000..18da885 --- /dev/null +++ b/src/command/mod.rs @@ -0,0 +1,154 @@ +pub mod init; +mod login; +mod pack; +mod publish; +pub mod utils; + +use self::init::{Init, InitMode}; +use self::login::login; +use self::pack::pack; +use self::publish::publish; +use error::Error; +use slog::Logger; +use std::result; +use PBAR; + +#[derive(Debug, StructOpt)] +pub enum Command { + #[structopt(name = "init")] + /// 🐣 initialize a package.json based on your compiled wasm! + Init { + path: Option, + + #[structopt(long = "scope", short = "s")] + scope: Option, + + #[structopt(long = "--skip-build")] + /// Do not build, only update metadata + skip_build: bool, + + #[structopt(long = "no-typescript")] + /// By default a *.d.ts file is generated for the generated JS file, but + /// this flag will disable generating this TypeScript file. + disable_dts: bool, + + #[structopt(long = "target", short = "t", default_value = "browser")] + /// Sets the target environment. [possible values: browser, nodejs] + target: String, + + #[structopt(long = "debug")] + /// Build without --release. + debug: bool, + }, + + #[structopt(name = "pack")] + /// 🍱 create a tar of your npm package but don't publish! + Pack { path: Option }, + + #[structopt(name = "publish")] + /// 🎆 pack up your npm package and publish! + Publish { path: Option }, + + #[structopt(name = "login", alias = "adduser", alias = "add-user")] + /// 👤 Add a registry user account! (aliases: adduser, add-user) + Login { + #[structopt(long = "registry", short = "r")] + /// Default: 'https://registry.npmjs.org/'. + /// The base URL of the npm package registry. If scope is also + /// specified, this registry will only be used for packages with that + /// scope. scope defaults to the scope of the project directory you're + /// currently in, if any. + registry: Option, + + #[structopt(long = "scope", short = "s")] + /// Default: none. + /// If specified, the user and login credentials given will be + /// associated with the specified scope. + scope: Option, + + #[structopt(long = "always-auth", short = "a")] + /// If specified, save configuration indicating that all requests to the + /// given registry should include authorization information. Useful for + /// private registries. Can be used with --registry and / or --scope + always_auth: bool, + + #[structopt(long = "auth-type", short = "t")] + /// Default: 'legacy'. + /// Type: 'legacy', 'sso', 'saml', 'oauth'. + /// What authentication strategy to use with adduser/login. Some npm + /// registries (for example, npmE) might support alternative auth + /// strategies besides classic username/password entry in legacy npm. + auth_type: Option, + }, +} + +pub fn run_wasm_pack(command: Command, log: &Logger) -> result::Result<(), Error> { + // Run the correct command based off input and store the result of it so that we can clear + // the progress bar then return it + let status = match command { + Command::Init { + path, + scope, + skip_build, + disable_dts, + target, + debug, + } => { + info!(&log, "Running init command..."); + info!( + &log, + "Path: {:?}, Scope: {:?}, Skip build: {}, Disable Dts: {}, Target: {}, Debug: {}", + &path, + &scope, + &skip_build, + &disable_dts, + &target, + debug + ); + let mode = if skip_build { + InitMode::Nobuild + } else { + InitMode::Normal + }; + Init::new(path, scope, disable_dts, target, debug).process(&log, mode) + } + Command::Pack { path } => { + info!(&log, "Running pack command..."); + info!(&log, "Path: {:?}", &path); + pack(path, &log) + } + Command::Publish { path } => { + info!(&log, "Running publish command..."); + info!(&log, "Path: {:?}", &path); + publish(path, &log) + } + Command::Login { + registry, + scope, + always_auth, + auth_type, + } => { + info!(&log, "Running login command..."); + info!( + &log, + "Registry: {:?}, Scope: {:?}, Always Auth: {}, Auth Type: {:?}", + ®istry, + &scope, + &always_auth, + &auth_type + ); + login(registry, scope, always_auth, auth_type, &log) + } + }; + + match status { + Ok(_) => {} + Err(ref e) => { + error!(&log, "{}", e); + PBAR.error(e.error_type())?; + } + } + + // Return the actual status of the program to the main function + status +} diff --git a/src/command/pack.rs b/src/command/pack.rs new file mode 100644 index 0000000..0c8c989 --- /dev/null +++ b/src/command/pack.rs @@ -0,0 +1,20 @@ +use command::utils::set_crate_path; +use error::Error; +use npm; +use slog::Logger; +use std::result; +use PBAR; + +pub fn pack(path: Option, log: &Logger) -> result::Result<(), Error> { + let crate_path = set_crate_path(path); + + info!(&log, "Packing up the npm package..."); + npm::npm_pack(&crate_path)?; + #[cfg(not(target_os = "windows"))] + info!(&log, "Your package is located at {}/pkg", &crate_path); + #[cfg(target_os = "windows")] + info!(&log, "Your package is located at {}\\pkg", &crate_path); + + PBAR.message("🎒 packed up your package!")?; + Ok(()) +} diff --git a/src/command/publish.rs b/src/command/publish.rs new file mode 100644 index 0000000..dd213d0 --- /dev/null +++ b/src/command/publish.rs @@ -0,0 +1,18 @@ +use command::utils::set_crate_path; +use error::Error; +use npm; +use slog::Logger; +use std::result; +use PBAR; + +pub fn publish(path: Option, log: &Logger) -> result::Result<(), Error> { + let crate_path = set_crate_path(path); + + info!(&log, "Publishing the npm package..."); + info!(&log, "npm info located in the npm debug log"); + npm::npm_publish(&crate_path)?; + info!(&log, "Published your package!"); + + PBAR.message("💥 published your package!")?; + Ok(()) +} diff --git a/src/command/utils.rs b/src/command/utils.rs new file mode 100644 index 0000000..ff85ef0 --- /dev/null +++ b/src/command/utils.rs @@ -0,0 +1,8 @@ +pub fn set_crate_path(path: Option) -> String { + let crate_path = match path { + Some(p) => p, + None => ".".to_string(), + }; + + crate_path +} diff --git a/src/error.rs b/src/error.rs index 0bd2ba6..a06b437 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,7 @@ use serde_json; use std::borrow::Cow; use std::io; +use std::sync::PoisonError; use toml; #[derive(Debug, Fail)] @@ -13,6 +14,8 @@ pub enum Error { SerdeJson(#[cause] serde_json::Error), #[fail(display = "{}", _0)] SerdeToml(#[cause] toml::de::Error), + #[fail(display = "Acquiring lock failed")] + PoisonedLockError, #[fail(display = "{}. stderr:\n\n{}", message, stderr)] Cli { message: String, stderr: String }, #[fail(display = "{}", message)] @@ -38,6 +41,7 @@ impl Error { Error::Io(_) => "There was an I/O error. Details:\n\n", Error::SerdeJson(_) => "There was an JSON error. Details:\n\n", Error::SerdeToml(_) => "There was an TOML error. Details:\n\n", + Error::PoisonedLockError => "There was an RwLock error. Details: \n\n", Error::Cli { message: _, stderr: _, @@ -66,3 +70,9 @@ impl From for Error { Error::SerdeToml(e) } } + +impl From> for Error { + fn from(_: PoisonError) -> Self { + Error::PoisonedLockError + } +} diff --git a/src/manifest.rs b/src/manifest.rs index 668947d..22c95c4 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -123,25 +123,24 @@ pub fn write_package_json( ) }; - let pb = PBAR.step(step, &msg); + PBAR.step(step, &msg)?; let pkg_file_path = format!("{}/pkg/package.json", path); let mut pkg_file = File::create(pkg_file_path)?; let crate_data = read_cargo_toml(path)?; let npm_data = crate_data.into_npm(scope, disable_dts); if npm_data.description.is_none() { - PBAR.warn(&warn_fmt("description")); + PBAR.warn(&warn_fmt("description"))?; } if npm_data.repository.is_none() { - PBAR.warn(&warn_fmt("repository")); + PBAR.warn(&warn_fmt("repository"))?; } if npm_data.license.is_none() { - PBAR.warn(&warn_fmt("license")); + PBAR.warn(&warn_fmt("license"))?; } let npm_json = serde_json::to_string_pretty(&npm_data)?; pkg_file.write_all(npm_json.as_bytes())?; - pb.finish(); Ok(()) } @@ -149,7 +148,15 @@ pub fn get_crate_name(path: &str) -> Result { Ok(read_cargo_toml(path)?.package.name) } -pub fn check_wasm_bindgen(path: &str) -> Result<(), Error> { +pub fn check_crate_config(path: &str, step: &Step) -> Result<(), Error> { + let msg = format!("{}Checking crate configuration...", emoji::WRENCH); + PBAR.step(&step, &msg)?; + check_wasm_bindgen(path)?; + check_crate_type(path)?; + Ok(()) +} + +fn check_wasm_bindgen(path: &str) -> Result<(), Error> { if read_cargo_toml(path)?.dependencies.map_or(false, |x| { !x.wasm_bindgen.unwrap_or("".to_string()).is_empty() }) { @@ -161,19 +168,14 @@ pub fn check_wasm_bindgen(path: &str) -> Result<(), Error> { )) } -fn has_cdylib(path: &str) -> Result { - Ok(read_cargo_toml(path)?.lib.map_or(false, |lib| { +fn check_crate_type(path: &str) -> Result<(), Error> { + if read_cargo_toml(path)?.lib.map_or(false, |lib| { lib.crate_type .map_or(false, |types| types.iter().any(|s| s == "cdylib")) - })) -} - -pub fn check_crate_type(path: &str) -> Result<(), Error> { - if !has_cdylib(path)? { - Error::crate_config( - "crate-type must include cdylib to compile to wasm32-unknown-unknown. Add the following to your Cargo.toml file:\n\n[lib]\ncrate-type = [\"cdylib\"]" - ) - } else { - Ok(()) + }) { + return Ok(()); } + Error::crate_config( + "crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your Cargo.toml file:\n\n[lib]\ncrate-type = [\"cdylib\"]" + ) } diff --git a/src/progressbar.rs b/src/progressbar.rs index a02a548..4bb49df 100644 --- a/src/progressbar.rs +++ b/src/progressbar.rs @@ -1,60 +1,90 @@ use console::style; use emoji; use error::Error; -use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use indicatif::{ProgressBar, ProgressStyle}; use std::fmt; +use std::sync::RwLock; pub struct ProgressOutput { - bar: MultiProgress, + spinner: RwLock, + messages: RwLock, } impl ProgressOutput { pub fn new() -> Self { Self { - bar: MultiProgress::new(), + spinner: RwLock::new(ProgressBar::new_spinner()), + messages: RwLock::new(String::from("")), } } - pub fn step(&self, step: &Step, message: &str) -> ProgressBar { + pub fn step(&self, step: &Step, message: &str) -> Result<(), Error> { let msg = format!("{} {}", style(step).bold().dim(), message); - self.bar.add(Self::progressbar(&msg)) + self.message(&msg) } - pub fn message(&self, message: &str) -> ProgressBar { - self.bar.add(Self::progressbar(message)) + fn finish(&self) -> Result<(), Error> { + let spinner = self.spinner.read()?; + spinner.finish(); + + let mut message = self.messages.write()?; + print!("{}", message); + message.clear(); + + Ok(()) + } + + pub fn message(&self, message: &str) -> Result<(), Error> { + self.finish()?; + + let mut spinner = self.spinner.write()?; + *spinner = Self::progressbar(message); + Ok(()) + } + + fn add_message(&self, msg: &str) -> Result<(), Error> { + let mut message = self.messages.write()?; + message.push_str(" "); + message.push_str(msg); + message.push('\n'); + + Ok(()) } - pub fn info(&self, message: &str) { + pub fn info(&self, message: &str) -> Result<(), Error> { let info = format!( "{} {}: {}", emoji::INFO, style("[INFO]").bold().dim(), message ); - let bar = self.bar.add(Self::progressbar(&info)); - bar.finish(); + self.add_message(&info)?; + + Ok(()) } - pub fn warn(&self, message: &str) { + pub fn warn(&self, message: &str) -> Result<(), Error> { let warn = format!( "{} {}: {}", emoji::WARN, style("[WARN]").bold().dim(), message ); - let bar = self.bar.add(Self::progressbar(&warn)); - bar.finish(); + self.add_message(&warn)?; + + Ok(()) } - pub fn error(&self, message: String) { + pub fn error(&self, message: String) -> Result<(), Error> { let err = format!( "{} {}: {}", emoji::ERROR, style("[ERR]").bold().dim(), message ); - let bar = self.bar.add(Self::progressbar(&err)); - bar.finish(); + self.add_message(&err)?; + + Ok(()) } fn progressbar(msg: &str) -> ProgressBar { @@ -70,7 +100,8 @@ impl ProgressOutput { } pub fn done(&self) -> Result<(), Error> { - self.bar.join_and_clear().map_err(|e| Error::from(e)) + self.finish()?; + Ok(()) } } @@ -93,3 +124,9 @@ impl fmt::Display for Step { write!(f, "[{}/{}]", self.current, self.total) } } + +impl Drop for ProgressOutput { + fn drop(&mut self) { + self.done().unwrap(); + } +} diff --git a/src/readme.rs b/src/readme.rs index 7e0cae2..5437a93 100644 --- a/src/readme.rs +++ b/src/readme.rs @@ -7,12 +7,11 @@ use PBAR; pub fn copy_from_crate(path: &str, step: &Step) -> Result<(), Error> { let msg = format!("{}Copying over your README...", emoji::DANCERS); - let pb = PBAR.step(step, &msg); + PBAR.step(step, &msg)?; let crate_readme_path = format!("{}/README.md", path); let new_readme_path = format!("{}/pkg/README.md", path); if let Err(_) = fs::copy(&crate_readme_path, &new_readme_path) { - PBAR.warn("origin crate has no README"); + PBAR.warn("origin crate has no README")?; }; - pb.finish(); Ok(()) } diff --git a/tests/manifest/main.rs b/tests/manifest/main.rs index ebc3e85..6f2b039 100644 --- a/tests/manifest/main.rs +++ b/tests/manifest/main.rs @@ -27,24 +27,27 @@ fn it_gets_the_crate_name_provided_path() { #[test] fn it_checks_has_cdylib_default_path() { - assert!(manifest::check_crate_type(".").is_err()); + let step = wasm_pack::progressbar::Step::new(1); + assert!(manifest::check_crate_config(".", &step).is_err()); } #[test] fn it_checks_has_cdylib_provided_path() { - assert!(manifest::check_crate_type("tests/fixtures/js-hello-world").is_ok()); + let step = wasm_pack::progressbar::Step::new(1); + assert!(manifest::check_crate_config("tests/fixtures/js-hello-world", &step).is_ok()); } #[test] fn it_checks_has_cdylib_wrong_crate_type() { - assert!(manifest::check_crate_type("tests/fixtures/bad-cargo-toml").is_err()); + let step = wasm_pack::progressbar::Step::new(1); + assert!(manifest::check_crate_config("tests/fixtures/bad-cargo-toml", &step).is_err()); } #[test] fn it_creates_a_package_json_default_path() { let step = wasm_pack::progressbar::Step::new(1); let path = ".".to_string(); - wasm_pack::command::create_pkg_dir(&path, &step).unwrap(); + wasm_pack::command::init::create_pkg_dir(&path, &step).unwrap(); assert!(manifest::write_package_json(&path, &None, false, &step).is_ok()); let package_json_path = format!("{}/pkg/package.json", &path); assert!(fs::metadata(package_json_path).is_ok()); @@ -66,7 +69,7 @@ fn it_creates_a_package_json_default_path() { fn it_creates_a_package_json_provided_path() { let step = wasm_pack::progressbar::Step::new(1); let path = "tests/fixtures/js-hello-world".to_string(); - wasm_pack::command::create_pkg_dir(&path, &step).unwrap(); + wasm_pack::command::init::create_pkg_dir(&path, &step).unwrap(); assert!(manifest::write_package_json(&path, &None, false, &step).is_ok()); let package_json_path = format!("{}/pkg/package.json", &path); assert!(fs::metadata(package_json_path).is_ok()); @@ -79,7 +82,7 @@ fn it_creates_a_package_json_provided_path() { fn it_creates_a_package_json_provided_path_with_scope() { let step = wasm_pack::progressbar::Step::new(1); let path = "tests/fixtures/scopes".to_string(); - wasm_pack::command::create_pkg_dir(&path, &step).unwrap(); + wasm_pack::command::init::create_pkg_dir(&path, &step).unwrap(); assert!(manifest::write_package_json(&path, &Some("test".to_string()), false, &step).is_ok()); let package_json_path = format!("{}/pkg/package.json", &path); assert!(fs::metadata(package_json_path).is_ok()); @@ -90,10 +93,12 @@ fn it_creates_a_package_json_provided_path_with_scope() { #[test] fn it_errors_when_wasm_bindgen_is_not_declared() { - assert!(manifest::check_wasm_bindgen("tests/fixtures/bad-cargo-toml").is_err()); + let step = wasm_pack::progressbar::Step::new(1); + assert!(manifest::check_crate_config("tests/fixtures/bad-cargo-toml", &step).is_err()); } #[test] fn it_does_not_error_when_wasm_bindgen_is_declared() { - assert!(manifest::check_wasm_bindgen("tests/fixtures/js-hello-world").is_ok()); + let step = wasm_pack::progressbar::Step::new(1); + assert!(manifest::check_crate_config("tests/fixtures/js-hello-world", &step).is_ok()); }