diff --git a/README.md b/README.md index 54dda12e..6298d68d 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ cargo install --git https://github.com/uqbar-dao/uqdev ## Usage ```bash -# Create a new package template (default Rust): +# Create a Rust package template (default no UI): uqdev new my_package -# Build the package ("--ui" is optional): -uqdev build my_package --ui +# Build the package: +uqdev build my_package # Start a fake node, by default, on port 8080: uqdev boot-fake-node @@ -38,16 +38,22 @@ uqdev start-package my_package --url http://localhost:8080 # Or build, start a node, and start a package from inside the project... cd my_package -uqdev build --ui +uqdev build uqdev boot-fake-node uqdev start-package -u http://localhost:8080 -# Create a Python package template (it `build`s & `start-package`s just like a Rust package!): +# Bonus: create a Python package template (it `build`s & `start-package`s just like a Rust package!): uqdev new my_py_package -l python cd my_py_package uqdev build uqdev start-package -u http://localhost:8080 +# Bonus: create a Rust package template with UI (it `build`s & `start-package`s just like a Rust package!): +uqdev new my_package_with_ui --ui +cd my_package_with_ui +uqdev build +uqdev start-package -u http://localhost:8080 + # Print usage uqdev --help diff --git a/src/build/mod.rs b/src/build/mod.rs index 7bca1661..b1507fc1 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -46,61 +46,6 @@ pub async fn download_file(url: &str, path: &Path) -> anyhow::Result<()> { Ok(()) } -pub fn compile_and_copy_ui(package_dir: &Path, verbose: bool) -> anyhow::Result<()> { - let ui_path = package_dir.join("ui"); - println!("Building UI in {:?}...", ui_path); - - if ui_path.exists() && ui_path.is_dir() && ui_path.join("package.json").exists() { - println!("UI directory found, running npm install..."); - - // Set the current directory to 'ui_path' for the command - run_command(Command::new("npm") - .arg("install") - .current_dir(&ui_path) // Set the working directory - .stdout(if verbose { Stdio::inherit() } else { Stdio::null() }) - .stderr(if verbose { Stdio::inherit() } else { Stdio::null() }) - )?; - - println!("Running npm run build:copy..."); - - // Similarly for 'npm run build:copy' - run_command(Command::new("npm") - .args(["run", "build:copy"]) - .current_dir(&ui_path) // Set the working directory - .stdout(if verbose { Stdio::inherit() } else { Stdio::null() }) - .stderr(if verbose { Stdio::inherit() } else { Stdio::null() }) - )?; - } else { - println!("'ui' directory not found or 'ui/package.json' does not exist"); - } - - Ok(()) -} - -pub async fn compile_package_and_ui(package_dir: &Path, verbose: bool) -> anyhow::Result<()> { - compile_package(package_dir, verbose).await?; - compile_and_copy_ui(package_dir, verbose)?; - Ok(()) -} - -pub async fn compile_package(package_dir: &Path, verbose: bool) -> anyhow::Result<()> { - let rust_src_path = "src/lib.rs"; - let python_src_path = "src/lib.py"; - for entry in package_dir.read_dir()? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - if path.join(&rust_src_path).exists() { - compile_rust_wasm_process(&path, verbose).await?; - } else if path.join(&python_src_path).exists() { - compile_python_wasm_process(&path, verbose).await?; - } - } - } - - Ok(()) -} - async fn compile_python_wasm_process( process_dir: &Path, verbose: bool, @@ -249,3 +194,75 @@ async fn compile_rust_wasm_process( println!("Done compiling Rust Uqbar process in {:?}.", process_dir); Ok(()) } + +fn compile_and_copy_ui(package_dir: &Path, verbose: bool) -> anyhow::Result<()> { + let ui_path = package_dir.join("ui"); + println!("Building UI in {:?}...", ui_path); + + if ui_path.exists() && ui_path.is_dir() && ui_path.join("package.json").exists() { + println!("UI directory found, running npm install..."); + + // Set the current directory to 'ui_path' for the command + run_command(Command::new("npm") + .arg("install") + .current_dir(&ui_path) // Set the working directory + .stdout(if verbose { Stdio::inherit() } else { Stdio::null() }) + .stderr(if verbose { Stdio::inherit() } else { Stdio::null() }) + )?; + + println!("Running npm run build:copy..."); + + // Similarly for 'npm run build:copy' + run_command(Command::new("npm") + .args(["run", "build:copy"]) + .current_dir(&ui_path) // Set the working directory + .stdout(if verbose { Stdio::inherit() } else { Stdio::null() }) + .stderr(if verbose { Stdio::inherit() } else { Stdio::null() }) + )?; + } else { + println!("'ui' directory not found or 'ui/package.json' does not exist"); + } + + Ok(()) +} + +async fn compile_package_and_ui(package_dir: &Path, verbose: bool) -> anyhow::Result<()> { + compile_package(package_dir, verbose).await?; + compile_and_copy_ui(package_dir, verbose)?; + Ok(()) +} + +async fn compile_package(package_dir: &Path, verbose: bool) -> anyhow::Result<()> { + let rust_src_path = "src/lib.rs"; + let python_src_path = "src/lib.py"; + for entry in package_dir.read_dir()? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + if path.join(&rust_src_path).exists() { + compile_rust_wasm_process(&path, verbose).await?; + } else if path.join(&python_src_path).exists() { + compile_python_wasm_process(&path, verbose).await?; + } + } + } + + Ok(()) +} + +pub async fn execute(package_dir: &Path, ui_only: bool, verbose: bool) -> anyhow::Result<()> { + let ui_dir = package_dir.join("ui"); + if !ui_dir.exists() { + if ui_only { + return Err(anyhow::anyhow!("uqdev build: can't build UI: no ui directory exists")); + } else { + compile_package(package_dir, verbose).await + } + } else { + if ui_only { + compile_and_copy_ui(package_dir, verbose) + } else { + compile_package_and_ui(package_dir, verbose).await + } + } +} diff --git a/src/main.rs b/src/main.rs index 808043f1..a07d76bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,17 +41,10 @@ async fn execute( }, Some(("build", build_matches)) => { let package_dir = PathBuf::from(build_matches.get_one::("DIR").unwrap()); - let ui = build_matches.get_one::("UI").unwrap_or(&false); let ui_only = build_matches.get_one::("UI_ONLY").unwrap_or(&false); let verbose = !build_matches.get_one::("QUIET").unwrap(); - if *ui_only { - build::compile_and_copy_ui(&package_dir, verbose) - } else if *ui { - build::compile_package_and_ui(&package_dir, verbose).await - } else { - build::compile_package(&package_dir, verbose).await - } + build::execute(&package_dir, *ui_only, verbose).await }, Some(("inject-message", inject_message_matches)) => { let url: &String = inject_message_matches.get_one("URL").unwrap(); @@ -75,6 +68,7 @@ async fn execute( let publisher = new_matches.get_one::("PUBLISHER").unwrap(); let language: new::Language = new_matches.get_one::("LANGUAGE").unwrap().into(); let template: new::Template = new_matches.get_one::("TEMPLATE").unwrap().into(); + let ui = new_matches.get_one::("UI").unwrap_or(&false); new::execute( new_dir, @@ -82,6 +76,7 @@ async fn execute( publisher.clone(), language.clone(), template.clone(), + *ui, ) }, Some(("run-tests", run_tests_matches)) => { @@ -203,12 +198,6 @@ async fn main() -> anyhow::Result<()> { .help("The package directory to build") .default_value(¤t_dir) ) - .arg(Arg::new("UI") - .action(ArgAction::SetTrue) - .long("ui") - .help("If set, build the web UI for the process") - .required(false) - ) .arg(Arg::new("UI_ONLY") .action(ArgAction::SetTrue) .long("ui-only") @@ -297,6 +286,12 @@ async fn main() -> anyhow::Result<()> { .value_parser(["chat"]) .default_value("chat") ) + .arg(Arg::new("UI") + .action(ArgAction::SetTrue) + .long("ui") + .help("If set, use the template with UI") + .required(false) + ) ) .subcommand(Command::new("run-tests") .about("Run Uqbar tests") diff --git a/src/new/mod.rs b/src/new/mod.rs index f10a6982..ebc2da23 100644 --- a/src/new/mod.rs +++ b/src/new/mod.rs @@ -55,6 +55,7 @@ pub fn execute( publisher: String, language: Language, template: Template, + ui: bool, ) -> anyhow::Result<()> { // Check if the directory already exists if new_dir.exists() { @@ -66,12 +67,24 @@ pub fn execute( return Err(anyhow::anyhow!(error)); } - let template_prefix = format!("{}/{}/", language.to_string(), template.to_string()); + let ui_infix = if ui { "ui".to_string() } else { "no-ui".to_string() }; + let template_prefix = format!( + "{}/{}/{}/", + language.to_string(), + ui_infix, + template.to_string(), + ); + let ui_prefix = format!( + "{}/{}/", + ui_infix, + template.to_string(), + ); let path_to_content: HashMap = PATH_TO_CONTENT .iter() .filter_map(|(k, v)| { k .strip_prefix(&template_prefix) + .or_else(|| k.strip_prefix(&ui_prefix)) .and_then(|stripped| { let key = stripped .replace("{package_name}", &package_name) @@ -84,6 +97,14 @@ pub fn execute( }) }) .collect(); + if ui && path_to_content.keys().filter(|p| !p.starts_with("ui")).count() == 0 { + // Only `ui/` exists + return Err(anyhow::anyhow!( + "uqdev new: cannot use `--ui` for {} {}; template does not exist", + language.to_string(), + template.to_string(), + )); + } // Create the template directory and subdirectories path_to_content diff --git a/src/new/templates/python/chat/.gitignore b/src/new/templates/python/no-ui/chat/.gitignore similarity index 100% rename from src/new/templates/python/chat/.gitignore rename to src/new/templates/python/no-ui/chat/.gitignore diff --git a/src/new/templates/python/chat/pkg/manifest.json b/src/new/templates/python/no-ui/chat/pkg/manifest.json similarity index 100% rename from src/new/templates/python/chat/pkg/manifest.json rename to src/new/templates/python/no-ui/chat/pkg/manifest.json diff --git a/src/new/templates/python/chat/pkg/metadata.json b/src/new/templates/python/no-ui/chat/pkg/metadata.json similarity index 100% rename from src/new/templates/python/chat/pkg/metadata.json rename to src/new/templates/python/no-ui/chat/pkg/metadata.json diff --git a/src/new/templates/python/chat/{package_name}/src/lib.py b/src/new/templates/python/no-ui/chat/{package_name}/src/lib.py similarity index 100% rename from src/new/templates/python/chat/{package_name}/src/lib.py rename to src/new/templates/python/no-ui/chat/{package_name}/src/lib.py diff --git a/src/new/templates/rust/chat/.gitignore b/src/new/templates/rust/no-ui/chat/.gitignore similarity index 100% rename from src/new/templates/rust/chat/.gitignore rename to src/new/templates/rust/no-ui/chat/.gitignore diff --git a/src/new/templates/rust/chat/pkg/manifest.json b/src/new/templates/rust/no-ui/chat/pkg/manifest.json similarity index 100% rename from src/new/templates/rust/chat/pkg/manifest.json rename to src/new/templates/rust/no-ui/chat/pkg/manifest.json diff --git a/src/new/templates/rust/chat/pkg/metadata.json b/src/new/templates/rust/no-ui/chat/pkg/metadata.json similarity index 100% rename from src/new/templates/rust/chat/pkg/metadata.json rename to src/new/templates/rust/no-ui/chat/pkg/metadata.json diff --git a/src/new/templates/rust/no-ui/chat/{package_name}/Cargo.toml b/src/new/templates/rust/no-ui/chat/{package_name}/Cargo.toml new file mode 100644 index 00000000..f3abdbec --- /dev/null +++ b/src/new/templates/rust/no-ui/chat/{package_name}/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "{package_name}" +version = "0.1.0" +edition = "2021" + +[profile.release] +panic = "abort" +opt-level = "s" +lto = true + +[dependencies] +anyhow = "1.0" +bincode = "1.3.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uqbar_process_lib = { git = "ssh://git@github.com/uqbar-dao/process_lib.git", rev = "e75fec6" } +wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "efcc759" } + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "uqbar:process" diff --git a/src/new/templates/rust/no-ui/chat/{package_name}/src/lib.rs b/src/new/templates/rust/no-ui/chat/{package_name}/src/lib.rs new file mode 100644 index 00000000..d61d7feb --- /dev/null +++ b/src/new/templates/rust/no-ui/chat/{package_name}/src/lib.rs @@ -0,0 +1,93 @@ +use serde::{Serialize, Deserialize}; + +use uqbar_process_lib::{await_message, print_to_terminal, Address, Message, ProcessId, Request, Response}; + +wit_bindgen::generate!({ + path: "wit", + world: "process", + exports: { + world: Component, + }, +}); + +#[derive(Debug, Serialize, Deserialize)] +enum ChatRequest { + Send { target: String, message: String }, + History, +} + +#[derive(Debug, Serialize, Deserialize)] +enum ChatResponse { + Ack, + History { messages: MessageArchive }, +} + +type MessageArchive = Vec<(String, String)>; + +fn handle_message ( + our: &Address, + message_archive: &mut MessageArchive, +) -> anyhow::Result<()> { + let message = await_message().unwrap(); + + match message { + Message::Response { .. } => { + print_to_terminal(0, &format!("{package_name}: unexpected Response: {:?}", message)); + panic!(""); + }, + Message::Request { ref source, ref ipc, .. } => { + match serde_json::from_slice(ipc)? { + ChatRequest::Send { ref target, ref message } => { + if target == &our.node { + print_to_terminal(0, &format!("{package_name}|{}: {}", source.node, message)); + message_archive.push((source.node.clone(), message.clone())); + } else { + let _ = Request::new() + .target(Address { + node: target.clone(), + process: ProcessId::from_str("{package_name}:{package_name}:{publisher}")?, + }) + .ipc(ipc.clone()) + .send_and_await_response(5)? + .unwrap(); + } + Response::new() + .ipc(serde_json::to_vec(&ChatResponse::Ack).unwrap()) + .send() + .unwrap(); + }, + ChatRequest::History => { + Response::new() + .ipc(serde_json::to_vec(&ChatResponse::History { + messages: message_archive.clone(), + }).unwrap()) + .send() + .unwrap(); + }, + } + }, + } + Ok(()) +} + +struct Component; +impl Guest for Component { + fn init(our: String) { + print_to_terminal(0, "{package_name}: begin"); + + let our = Address::from_str(&our).unwrap(); + let mut message_archive: MessageArchive = Vec::new(); + + loop { + match handle_message(&our, &mut message_archive) { + Ok(()) => {}, + Err(e) => { + print_to_terminal(0, format!( + "{package_name}: error: {:?}", + e, + ).as_str()); + }, + }; + } + } +} diff --git a/src/new/templates/rust/ui/chat/.gitignore b/src/new/templates/rust/ui/chat/.gitignore new file mode 100644 index 00000000..ed653657 --- /dev/null +++ b/src/new/templates/rust/ui/chat/.gitignore @@ -0,0 +1,6 @@ +*/target/ +pkg/*.wasm +*.swp +*.swo +*/wasi_snapshot_preview1.wasm +*/wit/ diff --git a/src/new/templates/rust/ui/chat/pkg/manifest.json b/src/new/templates/rust/ui/chat/pkg/manifest.json new file mode 100644 index 00000000..fdfa9893 --- /dev/null +++ b/src/new/templates/rust/ui/chat/pkg/manifest.json @@ -0,0 +1,13 @@ +[ + { + "process_name": "{package_name}", + "process_wasm_path": "/{package_name}.wasm", + "on_exit": "Restart", + "request_networking": true, + "request_messaging": [ + "net:sys:uqbar" + ], + "grant_messaging": [], + "public": true + } +] diff --git a/src/new/templates/rust/ui/chat/pkg/metadata.json b/src/new/templates/rust/ui/chat/pkg/metadata.json new file mode 100644 index 00000000..af3632bc --- /dev/null +++ b/src/new/templates/rust/ui/chat/pkg/metadata.json @@ -0,0 +1,5 @@ +{ + "package": "{package_name}", + "publisher": "{publisher}", + "version": [0, 1, 0] +} diff --git a/src/new/templates/rust/chat/{package_name}/Cargo.toml b/src/new/templates/rust/ui/chat/{package_name}/Cargo.toml similarity index 100% rename from src/new/templates/rust/chat/{package_name}/Cargo.toml rename to src/new/templates/rust/ui/chat/{package_name}/Cargo.toml diff --git a/src/new/templates/rust/chat/{package_name}/src/lib.rs b/src/new/templates/rust/ui/chat/{package_name}/src/lib.rs similarity index 100% rename from src/new/templates/rust/chat/{package_name}/src/lib.rs rename to src/new/templates/rust/ui/chat/{package_name}/src/lib.rs diff --git a/src/new/templates/rust/chat/ui/.eslintrc.cjs b/src/new/templates/ui/chat/ui/.eslintrc.cjs similarity index 100% rename from src/new/templates/rust/chat/ui/.eslintrc.cjs rename to src/new/templates/ui/chat/ui/.eslintrc.cjs diff --git a/src/new/templates/rust/chat/ui/.gitignore b/src/new/templates/ui/chat/ui/.gitignore similarity index 100% rename from src/new/templates/rust/chat/ui/.gitignore rename to src/new/templates/ui/chat/ui/.gitignore diff --git a/src/new/templates/rust/chat/ui/README.md b/src/new/templates/ui/chat/ui/README.md similarity index 100% rename from src/new/templates/rust/chat/ui/README.md rename to src/new/templates/ui/chat/ui/README.md diff --git a/src/new/templates/rust/chat/ui/index.html b/src/new/templates/ui/chat/ui/index.html similarity index 100% rename from src/new/templates/rust/chat/ui/index.html rename to src/new/templates/ui/chat/ui/index.html diff --git a/src/new/templates/rust/chat/ui/package-lock.json b/src/new/templates/ui/chat/ui/package-lock.json similarity index 100% rename from src/new/templates/rust/chat/ui/package-lock.json rename to src/new/templates/ui/chat/ui/package-lock.json diff --git a/src/new/templates/rust/chat/ui/package.json b/src/new/templates/ui/chat/ui/package.json similarity index 100% rename from src/new/templates/rust/chat/ui/package.json rename to src/new/templates/ui/chat/ui/package.json diff --git a/src/new/templates/rust/chat/ui/public/assets/vite.svg b/src/new/templates/ui/chat/ui/public/assets/vite.svg similarity index 100% rename from src/new/templates/rust/chat/ui/public/assets/vite.svg rename to src/new/templates/ui/chat/ui/public/assets/vite.svg diff --git a/src/new/templates/rust/chat/ui/src/App.css b/src/new/templates/ui/chat/ui/src/App.css similarity index 100% rename from src/new/templates/rust/chat/ui/src/App.css rename to src/new/templates/ui/chat/ui/src/App.css diff --git a/src/new/templates/rust/chat/ui/src/App.tsx b/src/new/templates/ui/chat/ui/src/App.tsx similarity index 100% rename from src/new/templates/rust/chat/ui/src/App.tsx rename to src/new/templates/ui/chat/ui/src/App.tsx diff --git a/src/new/templates/rust/chat/ui/src/assets/react.svg b/src/new/templates/ui/chat/ui/src/assets/react.svg similarity index 100% rename from src/new/templates/rust/chat/ui/src/assets/react.svg rename to src/new/templates/ui/chat/ui/src/assets/react.svg diff --git a/src/new/templates/rust/chat/ui/src/index.css b/src/new/templates/ui/chat/ui/src/index.css similarity index 100% rename from src/new/templates/rust/chat/ui/src/index.css rename to src/new/templates/ui/chat/ui/src/index.css diff --git a/src/new/templates/rust/chat/ui/src/main.tsx b/src/new/templates/ui/chat/ui/src/main.tsx similarity index 100% rename from src/new/templates/rust/chat/ui/src/main.tsx rename to src/new/templates/ui/chat/ui/src/main.tsx diff --git a/src/new/templates/rust/chat/ui/src/store/chat.ts b/src/new/templates/ui/chat/ui/src/store/chat.ts similarity index 100% rename from src/new/templates/rust/chat/ui/src/store/chat.ts rename to src/new/templates/ui/chat/ui/src/store/chat.ts diff --git a/src/new/templates/rust/chat/ui/src/types/Chat.ts b/src/new/templates/ui/chat/ui/src/types/Chat.ts similarity index 100% rename from src/new/templates/rust/chat/ui/src/types/Chat.ts rename to src/new/templates/ui/chat/ui/src/types/Chat.ts diff --git a/src/new/templates/rust/chat/ui/src/types/global.ts b/src/new/templates/ui/chat/ui/src/types/global.ts similarity index 100% rename from src/new/templates/rust/chat/ui/src/types/global.ts rename to src/new/templates/ui/chat/ui/src/types/global.ts diff --git a/src/new/templates/rust/chat/ui/src/vite-env.d.ts b/src/new/templates/ui/chat/ui/src/vite-env.d.ts similarity index 100% rename from src/new/templates/rust/chat/ui/src/vite-env.d.ts rename to src/new/templates/ui/chat/ui/src/vite-env.d.ts diff --git a/src/new/templates/rust/chat/ui/tsconfig.json b/src/new/templates/ui/chat/ui/tsconfig.json similarity index 100% rename from src/new/templates/rust/chat/ui/tsconfig.json rename to src/new/templates/ui/chat/ui/tsconfig.json diff --git a/src/new/templates/rust/chat/ui/tsconfig.node.json b/src/new/templates/ui/chat/ui/tsconfig.node.json similarity index 100% rename from src/new/templates/rust/chat/ui/tsconfig.node.json rename to src/new/templates/ui/chat/ui/tsconfig.node.json diff --git a/src/new/templates/rust/chat/ui/vite.config.ts b/src/new/templates/ui/chat/ui/vite.config.ts similarity index 100% rename from src/new/templates/rust/chat/ui/vite.config.ts rename to src/new/templates/ui/chat/ui/vite.config.ts diff --git a/src/run_tests/mod.rs b/src/run_tests/mod.rs index d6a6c879..f8cd9bce 100644 --- a/src/run_tests/mod.rs +++ b/src/run_tests/mod.rs @@ -279,10 +279,10 @@ pub async fn execute(config_path: &str) -> anyhow::Result<()> { for test in config.tests { for setup_package_path in &test.setup_package_paths { - build::compile_package(&setup_package_path, test.package_build_verbose).await?; + build::execute(&setup_package_path, false, test.package_build_verbose).await?; } for test_package_path in &test.test_package_paths { - build::compile_package(&test_package_path, test.package_build_verbose).await?; + build::execute(&test_package_path, false, test.package_build_verbose).await?; } // Initialize variables for master node and nodes list