diff --git a/src/commands/build/wranglerjs/mod.rs b/src/commands/build/wranglerjs/mod.rs index e8e850e9c..09c96c4d7 100644 --- a/src/commands/build/wranglerjs/mod.rs +++ b/src/commands/build/wranglerjs/mod.rs @@ -3,6 +3,7 @@ pub mod output; use crate::commands::build::watch::wait_for_changes; use crate::commands::build::watch::COOLDOWN_PERIOD; +use crate::commands::generate::run_generate; use crate::commands::publish::package::Package; use crate::install; @@ -128,7 +129,12 @@ fn setup_build(target: &Target) -> Result<(Command, PathBuf, Bundle), failure::E } let build_dir = target.build_dir()?; - run_npm_install(&build_dir.to_path_buf()).expect("could not run `npm install`"); + + if target.site.is_some() { + scaffold_site_worker(&target)?; + } + + run_npm_install(&build_dir).expect("could not run `npm install`"); let node = which::which("node").unwrap(); let mut command = Command::new(node); @@ -166,7 +172,7 @@ fn setup_build(target: &Target) -> Result<(Command, PathBuf, Bundle), failure::E if !bundle.has_webpack_config(&webpack_config_path) { let package = Package::new(&build_dir)?; let package_main = build_dir - .join(package.main()?) + .join(package.main(&build_dir)?) .to_str() .unwrap() .to_string(); @@ -182,6 +188,27 @@ fn setup_build(target: &Target) -> Result<(Command, PathBuf, Bundle), failure::E Ok((command, temp_file, bundle)) } +pub fn scaffold_site_worker(target: &Target) -> Result<(), failure::Error> { + let build_dir = target.build_dir()?; + // TODO: this is a placeholder template. Replace with The Real Thing on launch. + let template = "https://github.com/ashleymichal/glowing-palm-tree"; + + if !Path::new(&build_dir).exists() { + // TODO: use site.entry_point instead of build_dir explicitly. + run_generate( + build_dir.file_name().unwrap().to_str().unwrap(), + template, + &target.target_type, + )?; + + // This step is to prevent having a git repo within a git repo after + // generating the scaffold into an existing project. + fs::remove_dir_all(&build_dir.join(".git"))?; + } + + Ok(()) +} + // Run {npm install} in the specified directory. Skips the install if a // {node_modules} is found in the directory. fn run_npm_install(dir: &PathBuf) -> Result<(), failure::Error> { diff --git a/src/commands/generate.rs b/src/commands/generate.rs index 8122f1798..cc246e297 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -9,18 +9,29 @@ pub fn generate( name: &str, template: &str, target_type: Option, + site: bool, +) -> Result<(), failure::Error> { + let target_type = target_type.unwrap_or_else(|| get_target_type(template)); + run_generate(name, template, &target_type)?; + let config_path = PathBuf::from("./").join(&name); + Manifest::generate(name.to_string(), target_type, config_path, site)?; + Ok(()) +} + +pub fn run_generate( + name: &str, + template: &str, + target_type: &TargetType, ) -> Result<(), failure::Error> { let tool_name = "cargo-generate"; let binary_path = install::install(tool_name, "ashleygwilliams")?.binary(tool_name)?; let args = ["generate", "--git", template, "--name", name, "--force"]; - let target_type = target_type.unwrap_or_else(|| get_target_type(template)); let command = command(name, binary_path, &args, &target_type); let command_name = format!("{:?}", command); commands::run(command, &command_name)?; - Manifest::generate(name.to_string(), target_type, false)?; Ok(()) } diff --git a/src/commands/init.rs b/src/commands/init.rs index b39402048..d4fb65061 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,16 +1,29 @@ +use crate::commands; use crate::settings::target::{Manifest, TargetType}; use crate::terminal::message; -use std::path::Path; +use std::path::{Path, PathBuf}; -pub fn init(name: Option<&str>, target_type: Option) -> Result<(), failure::Error> { +pub fn init( + name: Option<&str>, + target_type: Option, + site: bool, +) -> Result<(), failure::Error> { if Path::new("./wrangler.toml").exists() { failure::bail!("A wrangler.toml file already exists! Please remove it before running this command again."); } let dirname = get_current_dirname()?; let name = name.unwrap_or_else(|| &dirname); let target_type = target_type.unwrap_or_default(); - Manifest::generate(name.to_string(), target_type, true)?; + let config_path = PathBuf::from("./"); + let manifest = Manifest::generate(name.to_string(), target_type, config_path, site)?; message::success("Succesfully created a `wrangler.toml`"); + + if site { + let env = None; + let release = false; + let target = manifest.get_target(env, release)?; + commands::build::wranglerjs::scaffold_site_worker(&target)?; + } Ok(()) } diff --git a/src/commands/kv/bucket/upload.rs b/src/commands/kv/bucket/upload.rs index b77da0acc..29f0ac7ef 100644 --- a/src/commands/kv/bucket/upload.rs +++ b/src/commands/kv/bucket/upload.rs @@ -75,7 +75,7 @@ pub fn upload_files( } // Add the popped key-value pair to the running batch of key-value pair uploads - key_count = key_count + 1; + key_count += 1; key_pair_bytes = key_pair_bytes + pair.key.len() + pair.value.len(); key_value_batch.push(pair); } diff --git a/src/commands/kv/namespace/site.rs b/src/commands/kv/namespace/site.rs index dafc4f6bc..0ae627202 100644 --- a/src/commands/kv/namespace/site.rs +++ b/src/commands/kv/namespace/site.rs @@ -25,17 +25,10 @@ pub fn site(target: &Target, user: &GlobalUser) -> Result { - return Ok(success.result); - } + Ok(success) => Ok(success.result), Err(e) => match e { ApiFailure::Error(_status, api_errors) => { - if api_errors - .errors - .iter() - .find(|&e| e.code == 10014) - .is_some() - { + if api_errors.errors.iter().any(|e| e.code == 10014) { log::info!("Namespace {} already exists.", title); let response = client.request(&ListNamespaces { account_identifier: &target.account_id, diff --git a/src/commands/publish/mod.rs b/src/commands/publish/mod.rs index 41a711ae3..1401dfbb7 100644 --- a/src/commands/publish/mod.rs +++ b/src/commands/publish/mod.rs @@ -11,7 +11,6 @@ use route::Route; use upload_form::build_script_upload_form; -use log::info; use std::path::Path; use crate::commands; @@ -29,7 +28,7 @@ pub fn publish( push_worker: bool, push_bucket: bool, ) -> Result<(), failure::Error> { - info!("workers_dev = {}", target.workers_dev); + log::info!("workers_dev = {}", target.workers_dev); validate_target(target)?; @@ -80,14 +79,14 @@ fn publish_script(user: &GlobalUser, target: &Target) -> Result<(), failure::Err let pattern = if !target.workers_dev { let route = Route::new(&target)?; Route::publish(&user, &target, &route)?; - info!("publishing to route"); + log::info!("publishing to route"); route.pattern } else { - info!("publishing to subdomain"); + log::info!("publishing to subdomain"); publish_to_subdomain(target, user)? }; - info!("{}", &pattern); + log::info!("{}", &pattern); message::success(&format!( "Successfully published your script to {}", &pattern @@ -100,7 +99,7 @@ fn upload_buckets(target: &Target, user: &GlobalUser) -> Result<(), failure::Err for namespace in &target.kv_namespaces() { if let Some(bucket) = &namespace.bucket { let path = Path::new(&bucket); - kv::bucket::upload(target, user.to_owned(), &namespace.id, path, false)?; + kv::bucket::sync(target, user.to_owned(), &namespace.id, path, false)?; } } @@ -112,7 +111,7 @@ fn build_subdomain_request() -> String { } fn publish_to_subdomain(target: &Target, user: &GlobalUser) -> Result { - info!("checking that subdomain is registered"); + log::info!("checking that subdomain is registered"); let subdomain = Subdomain::get(&target.account_id, user)?; let sd_worker_addr = format!( @@ -122,7 +121,7 @@ fn publish_to_subdomain(target: &Target, user: &GlobalUser) -> Result Result { + pub fn main(&self, build_dir: &PathBuf) -> Result { if self.main == "" { failure::bail!( "The `main` key in your `package.json` file is required; please specified the entrypoint of your Worker.", ) - } else if !Path::new(&self.main).exists() { + } else if !build_dir.join(&self.main).exists() { failure::bail!( "The entrypoint of your Worker ({}) could not be found.", self.main diff --git a/src/commands/publish/upload_form/mod.rs b/src/commands/publish/upload_form/mod.rs index 278e83100..c25c8caf6 100644 --- a/src/commands/publish/upload_form/mod.rs +++ b/src/commands/publish/upload_form/mod.rs @@ -43,7 +43,7 @@ pub fn build_script_upload_form(target: &Target) -> Result let build_dir = target.build_dir()?; let package = Package::new(&build_dir)?; - let script_path = package.main()?; + let script_path = package.main(&build_dir)?; let assets = ProjectAssets::new(script_path, Vec::new(), kv_namespaces)?; diff --git a/src/main.rs b/src/main.rs index 578ddaa18..362b70072 100644 --- a/src/main.rs +++ b/src/main.rs @@ -105,7 +105,8 @@ fn run() -> Result<(), failure::Error> { .about("List all namespaces on your Cloudflare account") ) ) - .subcommand(SubCommand::with_name("kv:key") + .subcommand( + SubCommand::with_name("kv:key") .about(&*format!( "{} Individually manage Workers KV key-value pairs", emoji::KEY @@ -319,6 +320,13 @@ fn run() -> Result<(), failure::Error> { .long("type") .takes_value(true) .help("the type of project you want generated"), + ) + .arg( + Arg::with_name("site") + .short("s") + .long("site") + .takes_value(false) + .help("initializes a Workers Sites project. Overrides `type` and `template`"), ), ) .subcommand( @@ -338,6 +346,13 @@ fn run() -> Result<(), failure::Error> { .long("type") .takes_value(true) .help("the type of project you want generated"), + ) + .arg( + Arg::with_name("site") + .short("s") + .long("site") + .takes_value(false) + .help("initializes a Workers Sites project. Overrides `type` and `template`"), ), ) .subcommand( @@ -455,32 +470,54 @@ fn run() -> Result<(), failure::Error> { commands::global_config(email, api_key)?; } else if let Some(matches) = matches.subcommand_matches("generate") { let name = matches.value_of("name").unwrap_or("worker"); - let target_type = match matches.value_of("type") { - Some(s) => Some(TargetType::from_str(&s.to_lowercase())?), - None => None, - }; + let site = matches.is_present("site"); - let default_template = "https://github.com/cloudflare/worker-template"; - let template = matches.value_of("template").unwrap_or(match target_type { - Some(ref pt) => match pt { - TargetType::Rust => "https://github.com/cloudflare/rustwasm-worker-template", + let (target_type, template) = if site { + // Workers Sites projects are always Webpack for now + let target_type = Some(TargetType::Webpack); + // template = "https://github.com/cloudflare/worker-sites-template"; + // TODO: this is a placeholder template. Replace with The Real Thing (^) on launch. + let template = "https://github.com/ashleymichal/scaling-succotash"; + + (target_type, template) + } else { + let target_type = match matches.value_of("type") { + Some(s) => Some(TargetType::from_str(&s.to_lowercase())?), + None => None, + }; + + let default_template = "https://github.com/cloudflare/worker-template"; + let template = matches.value_of("template").unwrap_or(match target_type { + Some(ref pt) => match pt { + TargetType::Rust => "https://github.com/cloudflare/rustwasm-worker-template", + _ => default_template, + }, _ => default_template, - }, - _ => default_template, - }); + }); + + (target_type, template) + }; info!( "Generate command called with template {}, and name {}", template, name ); - commands::generate(name, template, target_type)?; + + commands::generate(name, template, target_type, site)?; } else if let Some(matches) = matches.subcommand_matches("init") { let name = matches.value_of("name"); - let target_type = match matches.value_of("type") { - Some(s) => Some(settings::target::TargetType::from_str(&s.to_lowercase())?), - None => None, + let site = matches.is_present("site"); + let target_type = if site { + // Workers Sites projects are always Webpack for now + Some(TargetType::Webpack) + } else { + match matches.value_of("type") { + Some(s) => Some(settings::target::TargetType::from_str(&s.to_lowercase())?), + None => None, + } }; - commands::init(name, target_type)?; + + commands::init(name, target_type, site)?; } else if let Some(matches) = matches.subcommand_matches("build") { info!("Getting project settings"); let manifest = settings::target::Manifest::new(config_path)?; diff --git a/src/settings/target/mod.rs b/src/settings/target/mod.rs index 2748f6202..a92f54772 100644 --- a/src/settings/target/mod.rs +++ b/src/settings/target/mod.rs @@ -15,6 +15,8 @@ use serde::{Deserialize, Serialize}; use crate::terminal::emoji; use crate::terminal::message; +const SITE_ENTRY_POINT: &str = "workers-site"; + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Site { pub bucket: String, @@ -22,6 +24,15 @@ pub struct Site { pub entry_point: Option, } +impl Default for Site { + fn default() -> Site { + Site { + bucket: String::new(), + entry_point: Some(String::from(SITE_ENTRY_POINT)), + } + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Target { pub account_id: String, @@ -38,8 +49,6 @@ pub struct Target { pub site: Option, } -const SITE_BUILD_DIR: &str = "./workers-site"; - impl Target { pub fn kv_namespaces(&self) -> Vec { self.kv_namespaces.clone().unwrap_or_else(Vec::new) @@ -54,16 +63,16 @@ impl Target { pub fn build_dir(&self) -> Result { let current_dir = env::current_dir()?; // if `site` is configured, we want to isolate worker code - // and build artifacts from static site application code. + // and build artifacts away from static site application code. // if the user has configured `site.entry-point`, use that - // as the build directory. Otherwise use our the default - // stored as the const SITE_BUILD_DIR + // as the build directory. Otherwise use the default const + // SITE_BUILD_DIR match &self.site { Some(site_config) => Ok(current_dir.join( site_config .entry_point .to_owned() - .unwrap_or(SITE_BUILD_DIR.to_string()), + .unwrap_or_else(|| format!("./{}", SITE_ENTRY_POINT)), )), None => Ok(current_dir), } @@ -85,7 +94,7 @@ pub struct Environment { pub site: Option, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Manifest { pub account_id: String, pub env: Option>, @@ -120,6 +129,108 @@ impl Manifest { Ok(manifest) } + pub fn generate( + name: String, + target_type: TargetType, + config_path: PathBuf, + site: bool, + ) -> Result { + let site = if site { Some(Site::default()) } else { None }; + let manifest = Manifest { + account_id: String::new(), + env: None, + kv_namespaces: None, + name: name.clone(), + private: None, + target_type: target_type.clone(), + route: Some(String::new()), + routes: None, + webpack_config: None, + workers_dev: Some(true), + zone_id: Some(String::new()), + site, + }; + + let toml = toml::to_string(&manifest)?; + let config_file = config_path.join("wrangler.toml"); + + log::info!("Writing a wrangler.toml file at {}", config_file.display()); + fs::write(&config_file, &toml)?; + Ok(manifest) + } + + pub fn get_target( + &self, + environment_name: Option<&str>, + release: bool, + ) -> Result { + if release && self.workers_dev.is_some() { + failure::bail!(format!( + "{} The --release flag is not compatible with use of the workers_dev field.", + emoji::WARN + )) + } + + if release { + message::warn("--release will be deprecated."); + } + + // Site projects are always Webpack for now; don't let toml override this. + let target_type = match self.site { + Some(_) => TargetType::Webpack, + None => self.target_type.clone(), + }; + + let mut target = Target { + target_type, // MUST inherit + account_id: self.account_id.clone(), // MAY inherit + webpack_config: self.webpack_config.clone(), // MAY inherit + zone_id: self.zone_id.clone(), // MAY inherit + workers_dev: true, // MAY inherit + // importantly, the top level name will be modified + // to include the name of the environment + name: self.name.clone(), // MAY inherit + kv_namespaces: self.kv_namespaces.clone(), // MUST NOT inherit + route: None, // MUST NOT inherit + routes: self.routes.clone(), // MUST NOT inherit + site: self.site.clone(), // MUST NOT inherit + }; + + let environment = self.get_environment(environment_name)?; + + self.check_private(environment); + + let (route, workers_dev) = self.negotiate_zoneless(environment, release)?; + target.route = route; + target.workers_dev = workers_dev; + if let Some(environment) = environment { + target.name = if let Some(name) = &environment.name { + name.clone() + } else { + match environment_name { + Some(environment_name) => format!("{}-{}", self.name, environment_name), + None => failure::bail!("You must specify `name` in your wrangler.toml"), + } + }; + if let Some(account_id) = &environment.account_id { + target.account_id = account_id.clone(); + } + if environment.routes.is_some() { + target.routes = environment.routes.clone(); + } + if environment.webpack_config.is_some() { + target.webpack_config = environment.webpack_config.clone(); + } + if environment.zone_id.is_some() { + target.zone_id = environment.zone_id.clone(); + } + // don't inherit kv namespaces because it is an anti-pattern to use the same namespaces across multiple environments + target.kv_namespaces = environment.kv_namespaces.clone(); + } + + Ok(target) + } + fn get_environment( &self, environment_name: Option<&str>, @@ -247,105 +358,6 @@ impl Manifest { } } } - - pub fn get_target( - &self, - environment_name: Option<&str>, - release: bool, - ) -> Result { - if release && self.workers_dev.is_some() { - failure::bail!(format!( - "{} The --release flag is not compatible with use of the workers_dev field.", - emoji::WARN - )) - } - - if release { - message::warn("--release will be deprecated."); - } - - let mut target = Target { - target_type: self.target_type.clone(), // MUST inherit - account_id: self.account_id.clone(), // MAY inherit - webpack_config: self.webpack_config.clone(), // MAY inherit - zone_id: self.zone_id.clone(), // MAY inherit - workers_dev: true, // MAY inherit - // importantly, the top level name will be modified - // to include the name of the environment - name: self.name.clone(), // MAY inherit - kv_namespaces: self.kv_namespaces.clone(), // MUST NOT inherit - route: None, // MUST NOT inherit - routes: self.routes.clone(), // MUST NOT inherit - site: self.site.clone(), // MUST NOT inherit - }; - - let environment = self.get_environment(environment_name)?; - - self.check_private(environment); - - let (route, workers_dev) = self.negotiate_zoneless(environment, release)?; - target.route = route; - target.workers_dev = workers_dev; - if let Some(environment) = environment { - target.name = if let Some(name) = &environment.name { - name.clone() - } else { - match environment_name { - Some(environment_name) => format!("{}-{}", self.name, environment_name), - None => failure::bail!("You must specify `name` in your wrangler.toml"), - } - }; - if let Some(account_id) = &environment.account_id { - target.account_id = account_id.clone(); - } - if environment.routes.is_some() { - target.routes = environment.routes.clone(); - } - if environment.webpack_config.is_some() { - target.webpack_config = environment.webpack_config.clone(); - } - if environment.zone_id.is_some() { - target.zone_id = environment.zone_id.clone(); - } - // don't inherit kv namespaces because it is an anti-pattern to use the same namespaces across multiple environments - target.kv_namespaces = environment.kv_namespaces.clone(); - } - - Ok(target) - } - - pub fn generate( - name: String, - target_type: TargetType, - init: bool, - ) -> Result { - let manifest = Manifest { - account_id: String::new(), - env: None, - kv_namespaces: None, - name: name.clone(), - private: None, - target_type: target_type.clone(), - route: Some(String::new()), - routes: None, - webpack_config: None, - workers_dev: Some(true), - zone_id: Some(String::new()), - site: None, - }; - - let toml = toml::to_string(&manifest)?; - let config_path = if init { - PathBuf::from("./") - } else { - Path::new("./").join(&name) - }; - let config_file = config_path.join("wrangler.toml"); - - log::info!("Writing a wrangler.toml file at {}", config_file.display()); - fs::write(&config_file, &toml)?; - Ok(manifest) - } } fn read_config(config_path: &Path) -> Result {