diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 8133ff8..df2fdd7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,7 +8,6 @@ publish = false [dependencies] anyhow.workspace = true -askama = "0.12.0" ceramic-config = { path = "../ceramic-config", version = "0.1.52" } clap = { version = "4.1.4", features = ["derive"] } did-method-key = "0.2" diff --git a/cli/src/install/ceramic_app_template.rs b/cli/src/install/ceramic_app_template.rs index 99f1cd3..6d7bd63 100644 --- a/cli/src/install/ceramic_app_template.rs +++ b/cli/src/install/ceramic_app_template.rs @@ -1,18 +1,27 @@ +use crate::DidAndPrivateKey; use std::path::Path; use tokio::io::AsyncWriteExt; use crate::install::npm::npm_install; const REPO: &'static str = "https://github.com/ceramicstudio/EthDenver2023Demo"; -const ZIP_PATH: &'static str = "/archive/refs/heads/main.zip"; pub async fn install_ceramic_app_template( + key: &DidAndPrivateKey, working_directory: &Path, project_name: &str, + template_branch: &Option, daemon_config_file: impl AsRef, ) -> anyhow::Result<()> { log::info!("Setting up application template from {}", REPO); - let data = reqwest::get(format!("{}{}", REPO, ZIP_PATH)) + let zip_path = format!( + "/archive/refs/heads/{}.zip", + template_branch + .as_ref() + .map(|s| s.as_str()) + .unwrap_or("main") + ); + let data = reqwest::get(format!("{}{}", REPO, zip_path)) .await? .bytes() .await?; @@ -35,7 +44,7 @@ pub async fn install_ceramic_app_template( tokio::fs::rename(&unzip_dir, &output_dir).await?; - npm_install(&output_dir, &None).await?; + npm_install(&output_dir, &None, false).await?; let readme = output_dir.join("README.md"); let mut f = tokio::fs::OpenOptions::new() @@ -72,9 +81,17 @@ You can check out [Create Ceramic App repo](https://github.com/ceramicstudio/cre let demo_config_file = output_dir.join("composedb.config.json"); tokio::fs::copy(&daemon_config_file, &demo_config_file).await?; + let seed_file = output_dir.join("admin_seed.txt"); + tokio::fs::write(&seed_file, key.pk().as_bytes()).await?; + log::info!("Building composites"); - crate::install::models::build_composite(&working_directory, &output_dir).await?; + tokio::process::Command::new("npm") + .current_dir(&output_dir) + .arg("run") + .arg("dev") + .status() + .await?; log::info!( r#"Application demo is available at {}. To run the demo application diff --git a/cli/src/install/ceramic_daemon.rs b/cli/src/install/ceramic_daemon.rs index 0311d45..4899020 100644 --- a/cli/src/install/ceramic_daemon.rs +++ b/cli/src/install/ceramic_daemon.rs @@ -20,7 +20,7 @@ pub async fn install_ceramic_daemon( cfg: &Config, version: &Option, ceramic_config_file: &Path, - quiet: bool, + start_ceramic: Option, ) -> anyhow::Result>> { verify_db::verify(&cfg).await?; @@ -39,14 +39,14 @@ pub async fn install_ceramic_daemon( if let Some(v) = version.as_ref() { program.push_str(&format!("@{}", v.to_string())); } - npm_install_package(&working_directory, &program).await?; + npm_install_package(&working_directory, &program, true).await?; - let ans = if quiet { - true - } else { - Confirm::new(&format!("Would you like ceramic started as a daemon?")) + let ans = match start_ceramic { + Some(true) => true, + Some(false) => false, + None => Confirm::new(&format!("Would you like ceramic started as a daemon?")) .with_default(true) - .prompt()? + .prompt()?, }; let ceramic_path = working_directory diff --git a/cli/src/install/compose_db.rs b/cli/src/install/compose_db.rs index b27c55c..1b88ced 100644 --- a/cli/src/install/compose_db.rs +++ b/cli/src/install/compose_db.rs @@ -30,7 +30,7 @@ pub async fn install_compose_db( if let Some(v) = version.as_ref() { program.push_str(&format!("@{}", v.to_string())); } - npm_install_package(working_directory, &program).await?; + npm_install_package(working_directory, &program, true).await?; let env_file = working_directory.join("composedb.env"); let mut f = tokio::fs::OpenOptions::new() @@ -60,7 +60,7 @@ pub async fn install_compose_db( cfg.network.id.clone() }; let network_name = convert_network_identifier(&network_id_for_model_list); - + log::info!( r#" ComposeDB cli now available. diff --git a/cli/src/install/mod.rs b/cli/src/install/mod.rs index d8921b9..e8f5b73 100644 --- a/cli/src/install/mod.rs +++ b/cli/src/install/mod.rs @@ -1,7 +1,6 @@ pub mod ceramic_app_template; pub mod ceramic_daemon; pub mod compose_db; -mod models; mod npm; mod verify_db; diff --git a/cli/src/install/models.rs b/cli/src/install/models.rs deleted file mode 100644 index 11e58fc..0000000 --- a/cli/src/install/models.rs +++ /dev/null @@ -1,228 +0,0 @@ -use askama::Template; -use std::collections::HashMap; -use std::fmt::Formatter; -use std::path::{Path, PathBuf}; -use tokio::process::Command; - -#[derive(Template)] -#[template(path = "basic_profile.graphql.jinja")] -struct BasicProfile {} - -#[derive(Template)] -#[template(path = "following.graphql.jinja")] -struct Following<'a> { - #[allow(dead_code)] - model: &'a str, -} - -#[derive(Template)] -#[template(path = "posts.graphql.jinja")] -struct Posts<'a> { - #[allow(dead_code)] - model: &'a str, -} - -#[derive(Template)] -#[template(path = "posts_profile.graphql.jinja")] -struct PostsProfile<'a> { - #[allow(dead_code)] - profile_model: &'a str, - #[allow(dead_code)] - posts_model: &'a str, -} - -#[derive(serde::Deserialize)] -struct CreateOutput { - models: HashMap, -} - -struct PathWrapper { - path: PathBuf, - path_str: String, -} - -impl PathWrapper { - pub fn new(path: impl AsRef, file: &str) -> Self { - let path = path.as_ref().join(file); - let path_str = path.display().to_string(); - Self { path, path_str } - } - - pub fn as_str(&self) -> &str { - &self.path_str - } -} - -impl std::fmt::Display for PathWrapper { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.path.display()) - } -} - -impl AsRef for PathWrapper { - fn as_ref(&self) -> &Path { - &self.path - } -} - -fn composedb_command(working_directory: impl AsRef) -> Command { - let mut cmd = Command::new("sh"); - cmd.current_dir(working_directory.as_ref()); - cmd.args(&["composedb"]); - cmd -} - -fn create_command(working_directory: impl AsRef) -> Command { - let mut cmd = composedb_command(working_directory); - cmd.args(&["composite:create", "--output"]); - cmd -} - -pub async fn build_composite( - working_directory: impl AsRef, - app_dir: impl AsRef, -) -> anyhow::Result<()> { - let app_dir = app_dir.as_ref().join("src").join("__generated__"); - log::debug!( - "Building composite model in {}, using composedb from {}", - app_dir.display(), - working_directory.as_ref().display() - ); - if !tokio::fs::try_exists(&app_dir).await? { - tokio::fs::create_dir_all(&app_dir).await?; - } - - let basic_profile_path = PathWrapper::new(&app_dir, "basic_profile.graphql"); - let basic_profile = BasicProfile {}.render()?; - tokio::fs::write(&basic_profile_path, basic_profile.as_bytes()).await?; - let basic_profile_out = PathWrapper::new(&app_dir, "basic_profile.json"); - log::debug!( - "Creating BasicProfile model {} from schema {}", - basic_profile_out, - basic_profile_path - ); - let status = create_command(&working_directory) - .args(&[basic_profile_out.as_str(), basic_profile_path.as_str()]) - .status() - .await?; - if !status.success() { - anyhow::bail!("Failed to create basic_profile model"); - } - let create_output: CreateOutput = - serde_json::from_slice(tokio::fs::read(&basic_profile_out).await?.as_ref())?; - let profile_model = create_output - .models - .keys() - .next() - .ok_or_else(|| anyhow::anyhow!("No models created"))? - .to_string(); - - let following_path = PathWrapper::new(&app_dir, "following.graphql"); - let following = Following { - model: &profile_model, - } - .render()?; - tokio::fs::write(&following_path, following.as_bytes()).await?; - let following_out = PathWrapper::new(&app_dir, "following.json"); - log::debug!( - "Creating Following model {} from schema {}", - following_out, - following_path - ); - let status = create_command(&working_directory) - .args(&[following_out.as_str(), following_path.as_str()]) - .status() - .await?; - if !status.success() { - anyhow::bail!("Failed to create following model"); - } - - let posts_path = PathWrapper::new(&app_dir, "posts.graphql"); - let posts = Posts { - model: &profile_model, - } - .render()?; - tokio::fs::write(&posts_path, posts.as_bytes()).await?; - let posts_out = PathWrapper::new(&app_dir, "posts.json"); - log::debug!( - "Creating Posts model {} from schema {}", - posts_out, - posts_path - ); - let status = create_command(&working_directory) - .args(&[posts_out.as_str(), posts_path.as_str()]) - .status() - .await?; - if !status.success() { - anyhow::bail!("Failed to create posts model"); - } - let create_output: CreateOutput = - serde_json::from_slice(tokio::fs::read(&posts_out).await?.as_ref())?; - let posts_model = create_output - .models - .keys() - .next() - .ok_or_else(|| anyhow::anyhow!("No models created"))? - .to_string(); - - let posts_profile_path = PathWrapper::new(&app_dir, "posts_profile.graphql"); - let posts_profile = PostsProfile { - posts_model: &posts_model, - profile_model: &profile_model, - } - .render()?; - tokio::fs::write(&posts_profile_path, posts_profile.as_bytes()).await?; - let posts_profile_out = PathWrapper::new(&app_dir, "posts_profile.json"); - log::debug!( - "Creating PostsProfile model {} from schema {}", - posts_profile_out, - posts_profile_path - ); - let status = create_command(&working_directory) - .args(&[posts_profile_out.as_str(), posts_profile_path.as_str()]) - .status() - .await?; - if !status.success() { - anyhow::bail!("Failed to create posts profile model"); - } - - let merged_path = PathWrapper::new(&app_dir, "merged.json"); - let status = composedb_command(&working_directory) - .args(&[ - "composite:merge", - basic_profile_out.as_str(), - posts_out.as_str(), - following_out.as_str(), - posts_profile_out.as_str(), - "--output", - merged_path.as_str(), - ]) - .status() - .await?; - if !status.success() { - anyhow::bail!("Failed to merge models"); - } - - let status = composedb_command(&working_directory) - .args(&["composite:deploy", merged_path.as_str()]) - .status() - .await?; - if !status.success() { - anyhow::bail!("Failed to deploy merged model"); - } - - let runtime_path = PathWrapper::new(&app_dir, "definition.js"); - let status = composedb_command(&working_directory) - .args(&[ - "composite:compile", - merged_path.as_str(), - runtime_path.as_str(), - ]) - .status() - .await?; - if !status.success() { - anyhow::bail!("Failed to deploy merged model"); - } - - Ok(()) -} diff --git a/cli/src/install/npm.rs b/cli/src/install/npm.rs index d5a6d99..7d3622d 100644 --- a/cli/src/install/npm.rs +++ b/cli/src/install/npm.rs @@ -5,6 +5,7 @@ use tokio::process::Command; pub async fn npm_install_package( working_directory: impl AsRef, package: &str, + globally: bool, ) -> anyhow::Result<()> { let status = Command::new("npm") .args(&["init", "--yes"]) @@ -16,7 +17,7 @@ pub async fn npm_install_package( anyhow::bail!("Failed to init npm, cannot download {}", package); } - npm_install(working_directory, &Some(&package)).await?; + npm_install(working_directory, &Some(&package), globally).await?; Ok(()) } @@ -24,9 +25,13 @@ pub async fn npm_install_package( pub async fn npm_install( working_directory: impl AsRef, package: &Option<&str>, + globally: bool, ) -> anyhow::Result<()> { let msg = "Installing dependencies"; let mut args = vec!["install"]; + if globally { + args.push("-g"); + } let msg = if let Some(p) = package { args.push(p); format!("{} for {}", msg, p) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 746b037..6204ec7 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -15,6 +15,7 @@ use tokio::{io::AsyncWriteExt, task::JoinHandle}; pub struct Versions { pub ceramic: Option, pub composedb: Option, + pub template_branch: Option, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -45,7 +46,8 @@ pub async fn interactive_default( }; let did_sk_path = project.path.join("admin.sk"); - log::info!(r#"Welcome to Wheel! By default, wheel will have the following configuration: + log::info!( + r#"Welcome to Wheel! By default, wheel will have the following configuration: - Project Name: {} - Project Directory: {} - Network: {} @@ -53,7 +55,12 @@ pub async fn interactive_default( - ComposeDB Included - ComposeDB Sample Application Included - DID Generated Secret Key Path: {} -"#, project.name, project.path.display(), network_identifier, did_sk_path.display()); +"#, + project.name, + project.path.display(), + network_identifier, + did_sk_path.display() + ); let default_choice = Select::new( "Would you like to keep or change this configuration?", @@ -71,9 +78,7 @@ pub async fn interactive_default( log::info!("Exiting wheel"); std::process::exit(0); } - DefaultChoice::Change => { - interactive(working_directory, versions).await - } + DefaultChoice::Change => interactive(working_directory, versions).await, DefaultChoice::Keep => { if !tokio::fs::try_exists(&project.path).await? { log::info!( @@ -102,13 +107,14 @@ pub async fn interactive_default( doc, versions, true, + false, true, true, - false, - ).await + true, + ) + .await } } - } pub async fn interactive( @@ -210,6 +216,7 @@ Selection is used to setup project defaults"#) doc, versions, with_ceramic, + true, with_composedb, with_app_template, false, @@ -279,6 +286,7 @@ pub async fn quiet(opts: QuietOptions) -> anyhow::Result>> did, opts.versions, opts.with_ceramic, + true, opts.with_composedb, opts.with_app_template, true, @@ -293,6 +301,7 @@ async fn finish_setup( doc: DidAndPrivateKey, versions: Versions, with_ceramic: bool, + start_ceramic: bool, with_composedb: bool, with_app_template: bool, quiet: bool, @@ -311,13 +320,15 @@ async fn finish_setup( let daemon_config_file = write_daemon_config(&project.path, &cfg).await?; + let start_ceramic = if quiet { Some(start_ceramic) } else { None }; + let opt_child = if with_ceramic { install::ceramic_daemon::install_ceramic_daemon( &project.path, &cfg, &versions.ceramic, &daemon_config_file, - quiet, + start_ceramic, ) .await? } else { @@ -330,12 +341,11 @@ async fn finish_setup( } if with_app_template { - if opt_child.is_none() { - anyhow::bail!("Cannot install app template without ceramic daemon"); - } install::ceramic_app_template::install_ceramic_app_template( + &doc, &project.path, &project.name, + &versions.template_branch, &daemon_config_file, ) .await?; diff --git a/cli/src/main.rs b/cli/src/main.rs index e583d2a..3886ddd 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -86,6 +86,8 @@ struct ProgramArgs { ceramic_version: Option, #[arg(long)] composedb_version: Option, + #[arg(long)] + template_branch: Option, #[command(subcommand)] command: Option, } @@ -110,6 +112,9 @@ async fn main() -> anyhow::Result<()> { if let Some(v) = args.composedb_version { versions.composedb = Some(v.parse()?); } + if let Some(v) = args.template_branch { + versions.template_branch = Some(v); + } let opt_child = match args.command { None => {