diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f765d0a..323f59a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,6 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libdbus-1-dev - uses: actions/cache@v3 with: path: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c424d39..a71f331 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,6 +30,10 @@ jobs: --health-retries 50 steps: - uses: actions/checkout@v3 + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libdbus-1-dev - uses: actions/cache@v3 with: path: | diff --git a/Cargo.lock b/Cargo.lock index 5ce6901..6552061 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2671,6 +2671,7 @@ dependencies = [ "stellar-xdr", "strsim", "symlink", + "tempfile", "thiserror 1.0.69", "tokio", "tokio-stream", diff --git a/crates/loam-cli/Cargo.toml b/crates/loam-cli/Cargo.toml index ccd9bd4..c6cd702 100644 --- a/crates/loam-cli/Cargo.toml +++ b/crates/loam-cli/Cargo.toml @@ -69,11 +69,12 @@ rust-embed = { version = "8.2.0", features = ["debug-embed"] } regex = "1.10.5" toml_edit = "0.22.16" indexmap = { version = "1.9", features = ["serde"] } +tempfile = "3.8" +fs_extra = "1.3" [dev-dependencies] assert_cmd = "2.0.4" assert_fs = "1.0.7" -fs_extra = "1.3.0" predicates = "3.1.0" walkdir = "2.3" diff --git a/crates/loam-cli/src/commands/build/env_toml.rs b/crates/loam-cli/src/commands/build/env_toml.rs index 440db62..1cf9ae0 100644 --- a/crates/loam-cli/src/commands/build/env_toml.rs +++ b/crates/loam-cli/src/commands/build/env_toml.rs @@ -136,7 +136,7 @@ impl Environment { let current_env = parsed_toml.remove(loam_env); if current_env.is_none() { return Err(Error::NoSettingsForCurrentEnv(loam_env.to_string())); - }; + } Ok(current_env) } } diff --git a/crates/loam-cli/src/commands/init.rs b/crates/loam-cli/src/commands/init.rs index aff1a4d..3b6b673 100644 --- a/crates/loam-cli/src/commands/init.rs +++ b/crates/loam-cli/src/commands/init.rs @@ -2,10 +2,12 @@ use clap::Parser; use rust_embed::{EmbeddedFile, RustEmbed}; use soroban_cli::commands::contract::init as soroban_init; use std::{ - fs::{self, create_dir_all, metadata, read_to_string, remove_dir_all, write, Metadata}, + fs::{self, create_dir_all, metadata, read_to_string, write, Metadata}, io, path::{Path, PathBuf}, + process::Command, }; +use tempfile::TempDir; use toml_edit::{DocumentMut, TomlError}; const FRONTEND_TEMPLATE: &str = "https://github.com/loambuild/frontend"; @@ -38,6 +40,10 @@ pub enum Error { ConverBytesToStringErr(#[from] std::str::Utf8Error), #[error("Failed to parse toml file: {0}")] TomlParseError(#[from] TomlError), + #[error("Failed to copy frontend files: {0}")] + FrontendCopyError(String), + #[error("Git clone failed: {0}")] + GitCloneError(String), } impl Cmd { @@ -58,17 +64,20 @@ impl Cmd { project_path: self.project_path.to_string_lossy().to_string(), name: self.name.clone(), with_example: None, - frontend_template: Some(FRONTEND_TEMPLATE.to_string()), overwrite: true, + frontend_template: None, } .run(&soroban_cli::commands::global::Args::default())?; - // remove soroban hello_world default contract - remove_dir_all(self.project_path.join("contracts/hello_world/")).map_err(|e| { - eprintln!("Error removing directory"); - e + // Clone frontend template + let fe_template_dir = tempfile::tempdir().map_err(|e| { + eprintln!("Error creating temp dir for frontend template"); + Error::IoError(e) })?; + clone_repo(FRONTEND_TEMPLATE, fe_template_dir.path())?; + copy_frontend_files(&fe_template_dir, &self.project_path)?; + copy_example_contracts(&self.project_path)?; rename_cargo_toml_remove(&self.project_path, "core")?; rename_cargo_toml_remove(&self.project_path, "status_message")?; @@ -160,7 +169,6 @@ fn copy_file( Ok(()) } -// TODO: import from stellar-cli init (not currently pub there) fn file_exists(file_path: &Path) -> bool { metadata(file_path) .as_ref() @@ -175,3 +183,28 @@ fn rename_cargo_toml_remove(project: &Path, name: &str) -> Result<(), Error> { fs::rename(from, to)?; Ok(()) } + +fn clone_repo(repo_url: &str, dest: &Path) -> Result<(), Error> { + let status = Command::new("git") + .args(["clone", repo_url, dest.to_str().unwrap()]) + .status() + .map_err(|e| Error::GitCloneError(format!("Failed to execute git clone: {e}")))?; + + if !status.success() { + return Err(Error::GitCloneError("Git clone command failed".to_string())); + } + Ok(()) +} + +fn copy_frontend_files(temp_dir: &TempDir, project_path: &Path) -> Result<(), Error> { + fs_extra::dir::copy( + temp_dir.path(), + project_path, + &fs_extra::dir::CopyOptions::new() + .content_only(true) + .overwrite(true), + ) + .map_err(|e| Error::FrontendCopyError(e.to_string()))?; + + Ok(()) +} diff --git a/crates/loam-cli/src/commands/mod.rs b/crates/loam-cli/src/commands/mod.rs index 72c664a..63cb024 100644 --- a/crates/loam-cli/src/commands/mod.rs +++ b/crates/loam-cli/src/commands/mod.rs @@ -43,7 +43,7 @@ impl Root { Cmd::Build(build_info) => build_info.run().await?, Cmd::UpdateEnv(e) => e.run()?, Cmd::Dev(dev_info) => dev_info.run().await?, - }; + } Ok(()) } } diff --git a/crates/loam-cli/tests/it/unit.rs b/crates/loam-cli/tests/it/unit.rs index 01ba7d5..4bf569a 100644 --- a/crates/loam-cli/tests/it/unit.rs +++ b/crates/loam-cli/tests/it/unit.rs @@ -73,3 +73,35 @@ soroban_token_contract.client = false assert!(stderr.contains("🌐 using network at http://localhost:8000/rpc\n")); }); } + +#[test] +fn init_copies_contracts_and_frontend_template() { + let env = TestEnv::new_empty(); + + // Run loam init with project path + let project_path = env.cwd.join("my-project"); + env.loam("init") + .args([project_path.to_str().unwrap()]) + .assert() + .success(); + // Verify contract files exist + assert!(project_path.join("contracts/core/src/lib.rs").exists()); + assert!(project_path + .join("contracts/status_message/src/lib.rs") + .exists()); + assert!(project_path.join("contracts/core/Cargo.toml").exists()); + assert!(project_path + .join("contracts/status_message/Cargo.toml") + .exists()); + + // Verify frontend template files exist + assert!(project_path.join("package.json").exists()); + assert!(project_path.join("src").exists()); + assert!(project_path.join("tsconfig.json").exists()); + + // Verify Cargo.toml contains loam dependencies + let cargo_toml = std::fs::read_to_string(project_path.join("Cargo.toml")) + .expect("Should be able to read Cargo.toml"); + assert!(cargo_toml.contains("loam-sdk")); + assert!(cargo_toml.contains("loam-subcontract-core")); +} diff --git a/crates/loam-cli/tests/it/util.rs b/crates/loam-cli/tests/it/util.rs index e6c97d4..6e39fda 100644 --- a/crates/loam-cli/tests/it/util.rs +++ b/crates/loam-cli/tests/it/util.rs @@ -53,6 +53,14 @@ impl TestEnv { } } + pub fn new_empty() -> Self { + let temp_dir = TempDir::new().unwrap(); + Self { + cwd: temp_dir.path().to_path_buf(), + temp_dir, + } + } + pub fn from(template: &str, f: F) { let test_env = TestEnv::new(template); f(&test_env); diff --git a/examples/soroban/simple_account/src/subcontract.rs b/examples/soroban/simple_account/src/subcontract.rs index fb3f869..e2c466a 100644 --- a/examples/soroban/simple_account/src/subcontract.rs +++ b/examples/soroban/simple_account/src/subcontract.rs @@ -26,7 +26,7 @@ impl IsSimpleAccount for SimpleAccountManager { fn init(&mut self, public_key: BytesN<32>) -> Result<(), Error> { if self.owner.get().is_some() { return Err(Error::OwnerAlreadySet); - }; + } self.owner.set(&public_key); Ok(()) } diff --git a/justfile b/justfile index fb7d806..0c1fc69 100644 --- a/justfile +++ b/justfile @@ -11,7 +11,7 @@ path: just --list loam +args: - @cargo r -- {{args}} + @cargo r {{args}} s +args: @stellar {{args}} @@ -42,8 +42,8 @@ test-integration: build-cli-test-contracts create: build rm -rf .soroban - stellar keys generate default - just stellar contract deploy --wasm ./target/loam/example_core.wasm --alias core + -stellar keys generate default + just stellar contract deploy --wasm ./target/loam/example_core.wasm --alias core --source-account default -- --admin default # # Builds contracts. Deploys core subcontract and then redeploys to status message.