From 5a0d86a1dc9bac8490dce14d3ba5f7d08214c293 Mon Sep 17 00:00:00 2001 From: George Leung Date: Tue, 30 Jan 2024 22:30:04 -0500 Subject: [PATCH] init command generates a node project (#302) --- apps/create-moose-app/src/index.ts | 8 ++- apps/igloo-kit-cli/Cargo.lock | 54 +++++++++++++++++++ apps/igloo-kit-cli/Cargo.toml | 1 + apps/igloo-kit-cli/src/cli.rs | 26 +++++++++ .../src/cli/routines/initialize.rs | 2 +- apps/igloo-kit-cli/src/cli/routines/start.rs | 5 +- .../src/infrastructure/olap/clickhouse.rs | 2 +- apps/igloo-kit-cli/src/project.rs | 39 +++++++++----- apps/igloo-kit-cli/src/utilities.rs | 1 + apps/igloo-kit-cli/src/utilities/constants.rs | 2 +- apps/igloo-kit-cli/src/utilities/git.rs | 45 ++++++++++++++++ apps/igloo-kit-cli/tests/cli_init.rs | 4 +- 12 files changed, 168 insertions(+), 21 deletions(-) create mode 100644 apps/igloo-kit-cli/src/utilities/git.rs diff --git a/apps/create-moose-app/src/index.ts b/apps/create-moose-app/src/index.ts index 4d6b4a5e5..f70ed4455 100644 --- a/apps/create-moose-app/src/index.ts +++ b/apps/create-moose-app/src/index.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node import { spawnSync } from "child_process"; -import { mkdirSync } from "fs"; +import { mkdirSync, existsSync } from "fs"; /** * Returns the executable path which is located inside `node_modules` @@ -39,6 +39,12 @@ function run() { const args = process.argv.slice(2); const name = args[0]; if (name !== undefined) { + if (existsSync(name)) { + console.log( + `${name} already exists. Either try using a new name, or remove the directory.` + ); + process.exit(1); + } mkdirSync(name); } const processResult = spawnSync(getExePath(), ["init"].concat(args), { diff --git a/apps/igloo-kit-cli/Cargo.lock b/apps/igloo-kit-cli/Cargo.lock index 1238e1621..0c40e040d 100644 --- a/apps/igloo-kit-cli/Cargo.lock +++ b/apps/igloo-kit-cli/Cargo.lock @@ -232,6 +232,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -718,6 +719,21 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "git2" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" +dependencies = [ + "bitflags 2.4.1", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + [[package]] name = "globset" version = "0.4.13" @@ -1020,6 +1036,7 @@ dependencies = [ "diagnostics", "dialoguer", "fern", + "git2", "home", "http-body-util", "humantime", @@ -1128,6 +1145,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.65" @@ -1180,6 +1206,34 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libgit2-sys" +version = "0.16.1+1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libssh2-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.12" diff --git a/apps/igloo-kit-cli/Cargo.toml b/apps/igloo-kit-cli/Cargo.toml index 9e777d0f5..07a509f67 100644 --- a/apps/igloo-kit-cli/Cargo.toml +++ b/apps/igloo-kit-cli/Cargo.toml @@ -38,6 +38,7 @@ http-body-util = "0.1" lazy_static = "1.4.0" anyhow = "1.0" spinners = "4.1.1" +git2 = "0.18.1" [dev-dependencies] clickhouse = { version = "0.11.5", features = ["uuid", "test-util"] } diff --git a/apps/igloo-kit-cli/src/cli.rs b/apps/igloo-kit-cli/src/cli.rs index 06cfe15bc..7c048b35e 100644 --- a/apps/igloo-kit-cli/src/cli.rs +++ b/apps/igloo-kit-cli/src/cli.rs @@ -21,11 +21,14 @@ use self::{ }; use crate::cli::settings::init_config_file; use crate::project::Project; +use crate::utilities::git::is_git_repo; use clap::Parser; use commands::Commands; +use home::home_dir; use logger::setup_logging; use settings::{read_settings, Settings}; use std::path::Path; +use std::process::exit; #[derive(Parser)] #[command(author, version, about, long_about = None, arg_required_else_help(true))] @@ -60,6 +63,19 @@ async fn top_command_handler(settings: Settings, commands: &Commands) { ); let dir_path = Path::new(location); + + if dir_path.canonicalize().unwrap() == home_dir().unwrap().canonicalize().unwrap() { + show_message!( + MessageType::Error, + Message { + action: "Init".to_string(), + details: "You cannot create a project in your home directory" + .to_string(), + } + ); + exit(1); + } + let project = Project::from_dir(dir_path, name.clone(), *language); debug!("Project: {:?}", project); @@ -74,6 +90,16 @@ async fn top_command_handler(settings: Settings, commands: &Commands) { project .write_to_file() .expect("Failed to write project to file"); + + let is_git_repo = + is_git_repo(dir_path).expect("Failed to check if directory is a git repo"); + if !is_git_repo { + crate::utilities::git::create_init_commit(&project, dir_path); + show_message!( + MessageType::Success, + Message::new("Created".to_string(), "Git Repository".to_string()) + ); + } } Commands::Dev {} => { info!("Running dev command"); diff --git a/apps/igloo-kit-cli/src/cli/routines/initialize.rs b/apps/igloo-kit-cli/src/cli/routines/initialize.rs index fe2e9f96c..d832caee0 100644 --- a/apps/igloo-kit-cli/src/cli/routines/initialize.rs +++ b/apps/igloo-kit-cli/src/cli/routines/initialize.rs @@ -303,7 +303,7 @@ pub struct CreateDockerNetwork { } impl CreateDockerNetwork { - fn new(network_name: &'static str) -> Self { + pub fn new(network_name: &'static str) -> Self { Self { network_name } } } diff --git a/apps/igloo-kit-cli/src/cli/routines/start.rs b/apps/igloo-kit-cli/src/cli/routines/start.rs index aaa6bb697..5a06fee27 100644 --- a/apps/igloo-kit-cli/src/cli/routines/start.rs +++ b/apps/igloo-kit-cli/src/cli/routines/start.rs @@ -8,9 +8,9 @@ use super::{ Routine, RoutineFailure, RoutineSuccess, RunMode, }; use crate::cli::display::with_spinner; -use crate::cli::routines::initialize::CreateIglooTempDirectoryTree; +use crate::cli::routines::initialize::{CreateDockerNetwork, CreateIglooTempDirectoryTree}; use crate::cli::routines::util::ensure_docker_running; -use crate::utilities::constants::CLI_PROJECT_INTERNAL_DIR; +use crate::utilities::constants::{CLI_PROJECT_INTERNAL_DIR, PANDA_NETWORK}; use crate::{ cli::display::Message, project::Project, @@ -48,6 +48,7 @@ impl Routine for RunLocalInfrastructure { CreateIglooTempDirectoryTree::new(RunMode::Explicit {}, self.project.clone()) .run_explicit()?; ValidateMountVolumes::new(igloo_dir).run_explicit()?; + CreateDockerNetwork::new(PANDA_NETWORK).run_explicit()?; ValidatePandaHouseNetwork::new().run_explicit()?; RunRedPandaContainer::new(self.project.clone()).run_explicit()?; ValidateRedPandaRun::new().run_explicit()?; diff --git a/apps/igloo-kit-cli/src/infrastructure/olap/clickhouse.rs b/apps/igloo-kit-cli/src/infrastructure/olap/clickhouse.rs index 1c5fdb64f..9c4582195 100644 --- a/apps/igloo-kit-cli/src/infrastructure/olap/clickhouse.rs +++ b/apps/igloo-kit-cli/src/infrastructure/olap/clickhouse.rs @@ -237,7 +237,7 @@ pub async fn check_ready( crate::utilities::retry::retry( || run_query(&dummy_query, configured_client), |i, e| { - i < 5 + i < 10 && match e { clickhouse::error::Error::Network(v) => { let err_string = v.to_string(); diff --git a/apps/igloo-kit-cli/src/project.rs b/apps/igloo-kit-cli/src/project.rs index c0e3aa9e5..287e692e2 100644 --- a/apps/igloo-kit-cli/src/project.rs +++ b/apps/igloo-kit-cli/src/project.rs @@ -11,6 +11,7 @@ //! - `project_file_location` - The location of the project file on disk //! ``` +use std::collections::HashMap; use std::path::PathBuf; use crate::cli::local_webserver::LocalWebserverConfig; @@ -20,7 +21,7 @@ use crate::infrastructure::olap::clickhouse::config::ClickhouseConfig; use crate::infrastructure::stream::redpanda::RedpandaConfig; use crate::utilities::constants::{ - APP_DIR, APP_DIR_LAYOUT, CLI_PROJECT_INTERNAL_DIR, PROJECT_CONFIG_FILE, SCHEMAS_DIR, + APP_DIR, APP_DIR_LAYOUT, CLI_PROJECT_INTERNAL_DIR, PROJECT_CONFIG_FILE_TS, SCHEMAS_DIR, }; use config::{Config, ConfigError, File}; use log::debug; @@ -28,9 +29,13 @@ use serde::{Deserialize, Serialize}; use std::path::Path; #[derive(Serialize, Deserialize, Debug, Clone)] -struct ProjectConfigFile { +#[serde(rename_all = "camelCase")] +struct PackageJsonFile { pub name: String, - pub language: SupportedLanguages, + pub version: String, + pub scripts: HashMap, + pub dependencies: HashMap, + pub dev_dependencies: HashMap, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -69,7 +74,7 @@ impl Project { location = location .canonicalize() .expect("The directory provided does not exist"); - location.push(PROJECT_CONFIG_FILE); + location.push(PROJECT_CONFIG_FILE_TS); debug!("Project file location: {:?}", location); @@ -91,7 +96,7 @@ impl Project { pub fn load(directory: PathBuf) -> Result { let mut project_file = directory.clone(); - project_file.push(PROJECT_CONFIG_FILE); + project_file.push(PROJECT_CONFIG_FILE_TS); let project_file_location = project_file .clone() @@ -101,6 +106,8 @@ impl Project { let s = Config::builder() .add_source(File::from(project_file).required(true)) + // TODO: infer language from the project file + .set_default("language", "Typescript")? .set_override("project_file_location", project_file_location)? .build()?; @@ -137,7 +144,7 @@ impl Project { } // This is a Result of io::Error because the caller - // can be retruning a Result of io::Error or a Routine Failure + // can be returning a Result of io::Error or a Routine Failure pub fn internal_dir(&self) -> std::io::Result { let mut internal_dir = self.project_file_location.clone(); internal_dir.pop(); @@ -146,13 +153,13 @@ impl Project { if !internal_dir.is_dir() { if internal_dir.exists() { debug!("Internal dir exists as a file: {:?}", internal_dir); - std::io::Error::new( + return Err(std::io::Error::new( std::io::ErrorKind::Other, format!( "The {} file exists but is not a directory", CLI_PROJECT_INTERNAL_DIR ), - ); + )); } else { debug!("Creating internal dir: {:?}", internal_dir); std::fs::create_dir_all(&internal_dir)?; @@ -165,14 +172,20 @@ impl Project { } pub fn write_to_file(&self) -> Result<(), std::io::Error> { - let config_file = ProjectConfigFile { + let config_file = PackageJsonFile { name: self.name.clone(), - language: self.language, + version: "0.0".to_string(), + // For local development of the CLI + // change `igloo-cli` to `/apps/igloo-kit-cli/target/debug/igloo-cli` + scripts: HashMap::from([("dev".to_string(), "igloo-cli dev".to_string())]), + dependencies: HashMap::new(), + dev_dependencies: HashMap::from([( + "@514labs/moose-cli".to_string(), + "latest".to_string(), + )]), }; - let toml_project = toml::to_string(&config_file); - - match toml_project { + match serde_json::to_string_pretty(&config_file) { Ok(project) => { std::fs::write(&self.project_file_location, project)?; Ok(()) diff --git a/apps/igloo-kit-cli/src/utilities.rs b/apps/igloo-kit-cli/src/utilities.rs index cba5e0f4f..5fe0312b7 100644 --- a/apps/igloo-kit-cli/src/utilities.rs +++ b/apps/igloo-kit-cli/src/utilities.rs @@ -1,5 +1,6 @@ pub mod constants; pub mod docker; +pub mod git; pub mod package_managers; pub mod retry; pub mod system; diff --git a/apps/igloo-kit-cli/src/utilities/constants.rs b/apps/igloo-kit-cli/src/utilities/constants.rs index 18985c479..02f7af4ff 100644 --- a/apps/igloo-kit-cli/src/utilities/constants.rs +++ b/apps/igloo-kit-cli/src/utilities/constants.rs @@ -1,6 +1,6 @@ pub const CLI_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const PROJECT_CONFIG_FILE: &str = "project.toml"; +pub const PROJECT_CONFIG_FILE_TS: &str = "package.json"; pub const CLI_CONFIG_FILE: &str = "config.toml"; pub const CLI_USER_DIRECTORY: &str = ".igloo"; diff --git a/apps/igloo-kit-cli/src/utilities/git.rs b/apps/igloo-kit-cli/src/utilities/git.rs new file mode 100644 index 000000000..09225b923 --- /dev/null +++ b/apps/igloo-kit-cli/src/utilities/git.rs @@ -0,0 +1,45 @@ +use std::path::Path; + +use crate::framework::languages::SupportedLanguages; +use git2::{Error, Repository, Signature}; + +use crate::project::Project; + +pub fn is_git_repo(dir_path: &Path) -> Result { + match Repository::open(dir_path) { + Ok(_) => Ok(true), + Err(e) if e.code() == git2::ErrorCode::NotFound => Ok(false), + Err(e) => Err(e), + } +} + +pub fn create_init_commit(project: &Project, dir_path: &Path) { + let mut git_ignore_file = project.project_file_location.clone(); + git_ignore_file.pop(); + git_ignore_file.push(".gitignore"); + let mut git_ignore_entries = vec![".igloo"]; + git_ignore_entries.append(&mut match project.language { + SupportedLanguages::Typescript => vec!["node_modules", "dist", "coverage"], + }); + let mut git_ignore = git_ignore_entries.join("\n"); + git_ignore.push_str("\n\n"); + std::fs::write(git_ignore_file, git_ignore).unwrap(); + + let repo = Repository::init(dir_path).expect("Failed to initialize git repo"); + let author = + Signature::now("Moose CLI", "noreply@fiveonefour.com").expect("Failed to create signature"); + + // Now let's create an empty tree for this commit + let mut index = repo.index().expect("Failed to get repo index"); + index + .add_all(["."], git2::IndexAddOption::DEFAULT, None) + .expect("Failed to add path to index"); + index.write().expect("Failed to write index"); + let tree_id = index.write_tree().expect("Failed to write tree"); + + let tree = repo.find_tree(tree_id).expect("Failed to find tree"); + + // empty parent because it's the first commit + repo.commit(Some("HEAD"), &author, &author, "Initial commit", &tree, &[]) + .expect("Failed to create initial commit"); +} diff --git a/apps/igloo-kit-cli/tests/cli_init.rs b/apps/igloo-kit-cli/tests/cli_init.rs index 5b16cd64f..4d30fde4a 100644 --- a/apps/igloo-kit-cli/tests/cli_init.rs +++ b/apps/igloo-kit-cli/tests/cli_init.rs @@ -36,7 +36,7 @@ fn can_run_cli_init() -> Result<(), Box> { // TODO add more specific tests when the layout of the // app is more stable temp.child(".igloo").assert(predicate::path::exists()); - temp.child("project.toml").assert(predicate::path::exists()); + temp.child("package.json").assert(predicate::path::exists()); temp.child("app").assert(predicate::path::exists()); Ok(()) @@ -61,7 +61,7 @@ fn should_not_run_if_coming_soon_wall_is_blocking() -> Result<(), Box