From ee2abbd4e1b1df64d52484e3f56e412bb45165a5 Mon Sep 17 00:00:00 2001 From: Valerio Ageno Date: Tue, 4 Jun 2024 18:55:15 +0200 Subject: [PATCH] feat: tuono new command --- crates/tuono/Cargo.toml | 5 +- crates/tuono/src/lib.rs | 7 +- crates/tuono/src/scaffold_project.rs | 165 +++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 crates/tuono/src/scaffold_project.rs diff --git a/crates/tuono/Cargo.toml b/crates/tuono/Cargo.toml index 9fe92efc..309ff5ff 100644 --- a/crates/tuono/Cargo.toml +++ b/crates/tuono/Cargo.toml @@ -18,12 +18,15 @@ name = "tuono" path = "src/lib.rs" [dependencies] -clap = { version = "4.5.4", features = ["derive"] } +clap = { version = "4.5.4", features = ["derive", "cargo"] } watchexec = "4.0.0" miette = "7.2.0" watchexec-signals = "3.0.0" tokio = { version = "1", features = ["full"] } +serde = { version = "1.0.202", features = ["derive"] } watchexec-supervisor = "2.0.0" glob = "0.3.1" regex = "1.10.4" +reqwest = {version = "0.12.4", features =["blocking", "json"]} +serde_json = "1.0" diff --git a/crates/tuono/src/lib.rs b/crates/tuono/src/lib.rs index b8512405..4b4f2a23 100644 --- a/crates/tuono/src/lib.rs +++ b/crates/tuono/src/lib.rs @@ -5,6 +5,7 @@ mod source_builder; use source_builder::{bundle_axum_source, create_client_entry_files}; use self::source_builder::check_tuono_folder; +mod scaffold_project; mod watch; #[derive(Subcommand, Debug)] @@ -14,7 +15,7 @@ enum Actions { /// Build the production assets Build, /// Scaffold a new project - New, + New { folder_name: Option }, } #[derive(Parser, Debug)] @@ -45,8 +46,8 @@ pub fn cli() -> std::io::Result<()> { let mut vite_build = Command::new("./node_modules/.bin/tuono-build-prod"); let _ = &vite_build.output()?; } - Actions::New => { - println!("Scaffold new project") + Actions::New { folder_name } => { + scaffold_project::create_new_project(folder_name); } } diff --git a/crates/tuono/src/scaffold_project.rs b/crates/tuono/src/scaffold_project.rs new file mode 100644 index 00000000..090927ee --- /dev/null +++ b/crates/tuono/src/scaffold_project.rs @@ -0,0 +1,165 @@ +use clap::crate_version; +use reqwest::blocking; +use serde::Deserialize; +use std::env; +use std::fs::{self, create_dir, File, OpenOptions}; +use std::io::{self, prelude::*}; +use std::path::{Path, PathBuf}; + +const GITHUB_TUONO_REPO_URL: &str = + "https://api.github.com/repos/Valerioageno/tuono/git/trees/main?recursive=1"; + +const GITHUB_RAW_CONTENT_URL: &str = "https://raw.githubusercontent.com/Valerioageno/tuono/main/"; + +#[derive(Deserialize, Debug)] +enum GithubFileType { + #[serde(rename = "blob")] + Blob, + #[serde(rename = "tree")] + Tree, +} + +#[derive(Deserialize, Debug)] +struct GithubResponse { + tree: Vec, +} + +#[derive(Deserialize, Debug)] +struct GithubFile { + path: String, + #[serde(rename(deserialize = "type"))] + element_type: GithubFileType, +} + +fn create_file(path: PathBuf, content: String) -> std::io::Result<()> { + let mut file = File::create(path)?; + let _ = file.write_all(content.as_bytes()); + + Ok(()) +} + +pub fn create_new_project(folder_name: Option) { + let folder = folder_name.unwrap_or(".".to_string()); + + if folder != "." { + create_dir(&folder).unwrap(); + } + + let client = blocking::Client::builder() + .user_agent("") + .build() + .expect("Failed to build reqwest client"); + + let res = client + .get(GITHUB_TUONO_REPO_URL) + .send() + .expect("Failed to call the folder github API") + .json::>() + .expect("Failed to parse the repo structure"); + + let new_project_files = res + .tree + .iter() + .filter(|GithubFile { path, .. }| { + // TODO: Handle custom example download by CLI argument --template + if path.starts_with("examples/tuono/") { + return true; + } + false + }) + .collect::>(); + + let folder_name = PathBuf::from(&folder); + let current_dir = env::current_dir().expect("Failed to get current working directory"); + + let folder_path = current_dir.join(folder_name); + + create_directories(&new_project_files, &folder_path).expect("Failed to create directories"); + + for GithubFile { + element_type, path, .. + } in new_project_files.iter() + { + if let GithubFileType::Blob = element_type { + let file_content = client + .get(format!("{GITHUB_RAW_CONTENT_URL}{path}")) + .send() + .expect("Failed to call the folder github API") + .text() + .expect("Failed to parse the repo structure"); + + let path = PathBuf::from(&path.replace("examples/tuono/", "")); + + let file_path = folder_path.join(&path); + + create_file(file_path, file_content).expect("failed to create file"); + } + } + + update_package_json_version(&folder_path).expect("Failed to update package.json version"); + update_cargo_toml_version(&folder_path).expect("Failed to update Cargo.toml version"); + outro(folder); +} + +fn create_directories(new_project_files: &Vec<&GithubFile>, folder_path: &Path) -> io::Result<()> { + for GithubFile { + element_type, path, .. + } in new_project_files.iter() + { + if let GithubFileType::Tree = element_type { + let path = PathBuf::from(&path.replace("examples/tuono/", "")); + + let dir_path = folder_path.join(&path); + create_dir(&dir_path).unwrap(); + } + } + Ok(()) +} + +fn update_package_json_version(folder_path: &Path) -> io::Result<()> { + let v = crate_version!(); + let package_json_path = folder_path.join(PathBuf::from("package.json")); + let package_json = fs::read_to_string(&package_json_path)?; + let package_json = package_json.replace("workspace:*", v); + + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(package_json_path)?; + + file.write_all(package_json.as_bytes())?; + + Ok(()) +} + +fn update_cargo_toml_version(folder_path: &Path) -> io::Result<()> { + let v = crate_version!(); + let cargo_toml_path = folder_path.join(PathBuf::from("Cargo.toml")); + let cargo_toml = fs::read_to_string(&cargo_toml_path)?; + let cargo_toml = + cargo_toml.replace("{ path = \"../../crates/tuono_lib/\"}", &format!("\"{v}\"")); + + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(cargo_toml_path)?; + + file.write_all(cargo_toml.as_bytes())?; + + Ok(()) +} + +fn outro(folder_name: String) { + println!("Success! 🎉"); + + if folder_name != "." { + println!("\nGo to the project directory:"); + println!("cd {folder_name}/"); + } + + println!("\nInstall the dependencies:"); + println!("pnpm install"); + + println!("\nRun the local environment:"); + println!("tuono dev"); +}