Repository: redbadger/crux (language: rust)

Issue:
Could not pnpm install: Os
Hi,

I am trying to build the counter example, and I am getting the error below:

```
error: failed to run custom build command for `shared_types v0.1.0 (/crux/examples/simple_counter/shared_types)`

Caused by:
  process didn't exit successfully: `/crux/examples/simple_counter/target/debug/build/shared_types-b6ac82c2067f0340/build-script-build` (exit status: 101)
  --- stdout
  cargo:rerun-if-changed=../shared

  --- stderr
  thread 'main' panicked at /.cargo/registry/src/index.crates.io-6f17d22bba15001f/crux_core-0.6.5/src/typegen.rs:484:14:
  Could not pnpm install: Os { code: 2, kind: NotFound, message: "No such file or directory" }
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

Seems like I am missing a something?


Discussion / hints:
Manually install pnpm resolves the issue. 
We should probably catch this error and convert it to a friendlier "Please install pnpm" message or similar
Yeah good point. I'll raise a PR

Fix patch:
diff --git a/crux_core/src/typegen.rs b/crux_core/src/typegen.rs
index dcd384c0..90c7d9f6 100644
--- a/crux_core/src/typegen.rs
+++ b/crux_core/src/typegen.rs
@@ -50,25 +50,22 @@
 //!use uuid::Uuid;
 //!
 //!#[test]
-//!fn generate_types() {
+//!fn generate_types() -> anyhow::Result<()> {
 //!    let mut gen = TypeGen::new();
 //!
 //!    let sample_events = vec![Event::SendUuid(Uuid::new_v4())];
-//!    gen.register_type_with_samples(sample_events).unwrap();
+//!    gen.register_type_with_samples(sample_events)?;
 //!
-//!    gen.register_app::<App>().unwrap();
+//!    gen.register_app::<App>()?;
 //!
-//!    let temp = assert_fs::TempDir::new().unwrap();
+//!    let temp = assert_fs::TempDir::new()?;
 //!    let output_root = temp.join("crux_core_typegen_test");
 //!
-//!    gen.swift("SharedTypes", output_root.join("swift"))
-//!        .expect("swift type gen failed");
+//!    gen.swift("SharedTypes", output_root.join("swift"))?;
 //!
-//!    gen.java("com.example.counter.shared_types", output_root.join("java"))
-//!        .expect("java type gen failed");
+//!    gen.java("com.example.counter.shared_types", output_root.join("java"))?;
 //!
-//!    gen.typescript("shared_types", output_root.join("typescript"))
-//!        .expect("typescript type gen failed");
+//!    gen.typescript("shared_types", output_root.join("typescript"))?;
 //!}
 //! ```
 
@@ -110,6 +107,8 @@ pub enum TypeGenError {
     Generation(String),
     #[error("error writing generated types")]
     Io(#[from] std::io::Error),
+    #[error("`pnpm` is needed for TypeScript type generation, but it could not be found in PATH.\nPlease install it from https://pnpm.io/installation")]
+    PnpmNotFound(#[source] std::io::Error),
 }
 
 #[derive(Debug)]
@@ -309,8 +308,8 @@ impl TypeGen {
     /// # use std::env::temp_dir;
     /// # let mut gen = TypeGen::new();
     /// # let output_root = temp_dir().join("crux_core_typegen_doctest");
-    /// gen.swift("SharedTypes", output_root.join("swift"))
-    ///     .expect("swift type gen failed");
+    /// gen.swift("SharedTypes", output_root.join("swift"))?;
+    /// # Ok::<(), crux_core::typegen::TypeGenError>(())
     /// ```
     pub fn swift(&mut self, module_name: &str, path: impl AsRef<Path>) -> Result {
         self.ensure_registry()?;
@@ -373,8 +372,8 @@ impl TypeGen {
     /// gen.java(
     ///     "com.redbadger.crux_core.shared_types",
     ///     output_root.join("java"),
-    /// )
-    /// .expect("java type gen failed");
+    /// )?;
+    /// # Ok::<(), crux_core::typegen::TypeGenError>(())
     /// ```
     pub fn java(&mut self, package_name: &str, path: impl AsRef<Path>) -> Result {
         self.ensure_registry()?;
@@ -429,8 +428,8 @@ impl TypeGen {
     /// # use std::env::temp_dir;
     /// # let mut gen = TypeGen::new();
     /// # let output_root = temp_dir().join("crux_core_typegen_doctest");
-    /// gen.typescript("shared_types", output_root.join("typescript"))
-    ///    .expect("typescript type gen failed");
+    /// gen.typescript("shared_types", output_root.join("typescript"))?;
+    /// # Ok::<(), crux_core::typegen::TypeGenError>(())
     /// ```
     pub fn typescript(&mut self, module_name: &str, path: impl AsRef<Path>) -> Result {
         self.ensure_registry()?;
@@ -448,7 +447,7 @@ impl TypeGen {
 
         let extensions_dir =
             PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("typegen_extensions/typescript");
-        copy(extensions_dir, path).expect("Could not copy TS runtime");
+        copy(extensions_dir, path)?;
 
         let registry = match &self.state {
             State::Generating(registry) => registry,
@@ -481,7 +480,10 @@ impl TypeGen {
             .current_dir(output_dir.clone())
             .arg("install")
             .status()
-            .expect("Could not pnpm install");
+            .map_err(|e| match e.kind() {
+                std::io::ErrorKind::NotFound => TypeGenError::PnpmNotFound(e),
+                _ => TypeGenError::Io(e),
+            })?;
 
         // Build TS code and emit declarations
         std::process::Command::new("pnpm")
@@ -490,7 +492,7 @@ impl TypeGen {
             .arg("tsc")
             .arg("--build")
             .status()
-            .expect("Could tsc --build");
+            .map_err(TypeGenError::Io)?;
 
         Ok(())
     }
diff --git a/examples/bridge_echo/shared_types/build.rs b/examples/bridge_echo/shared_types/build.rs
index 3d0f5afd..6015b71f 100644
--- a/examples/bridge_echo/shared_types/build.rs
+++ b/examples/bridge_echo/shared_types/build.rs
@@ -2,24 +2,23 @@ use crux_core::typegen::TypeGen;
 use shared::App;
 use std::path::PathBuf;
 
-fn main() {
+fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=../shared");
 
     let mut gen = TypeGen::new();
 
-    gen.register_app::<App>().expect("register");
+    gen.register_app::<App>()?;
 
     let output_root = PathBuf::from("./generated");
 
-    gen.swift("SharedTypes", output_root.join("swift"))
-        .expect("swift type gen failed");
+    gen.swift("SharedTypes", output_root.join("swift"))?;
 
     gen.java(
         "com.example.bridge_echo.shared_types",
         output_root.join("java"),
-    )
-    .expect("java type gen failed");
+    )?;
 
-    gen.typescript("shared_types", output_root.join("typescript"))
-        .expect("typescript type gen failed");
+    gen.typescript("shared_types", output_root.join("typescript"))?;
+
+    Ok(())
 }
diff --git a/examples/cat_facts/shared_types/build.rs b/examples/cat_facts/shared_types/build.rs
index cba79fa3..a8d30789 100644
--- a/examples/cat_facts/shared_types/build.rs
+++ b/examples/cat_facts/shared_types/build.rs
@@ -2,24 +2,23 @@ use crux_core::typegen::TypeGen;
 use shared::CatFacts;
 use std::path::PathBuf;
 
-fn main() {
+fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=../shared");
 
     let mut gen = TypeGen::new();
 
-    gen.register_app::<CatFacts>().expect("register");
+    gen.register_app::<CatFacts>()?;
 
     let output_root = PathBuf::from("./generated");
 
-    gen.swift("SharedTypes", output_root.join("swift"))
-        .expect("swift type gen failed");
+    gen.swift("SharedTypes", output_root.join("swift"))?;
 
     gen.java(
         "com.redbadger.catfacts.shared_types",
         output_root.join("java"),
-    )
-    .expect("java type gen failed");
+    )?;
 
-    gen.typescript("shared_types", output_root.join("typescript"))
-        .expect("typescript type gen failed");
+    gen.typescript("shared_types", output_root.join("typescript"))?;
+
+    Ok(())
 }
diff --git a/examples/counter/shared_types/build.rs b/examples/counter/shared_types/build.rs
index 8f77ea14..b6395877 100644
--- a/examples/counter/shared_types/build.rs
+++ b/examples/counter/shared_types/build.rs
@@ -2,21 +2,20 @@ use crux_core::typegen::TypeGen;
 use shared::App;
 use std::path::PathBuf;
 
-fn main() {
+fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=../shared");
 
     let mut gen = TypeGen::new();
 
-    gen.register_app::<App>().expect("register");
+    gen.register_app::<App>()?;
 
     let output_root = PathBuf::from("./generated");
 
-    gen.swift("SharedTypes", output_root.join("swift"))
-        .expect("swift type gen failed");
+    gen.swift("SharedTypes", output_root.join("swift"))?;
 
-    gen.java("com.example.counter.shared_types", output_root.join("java"))
-        .expect("java type gen failed");
+    gen.java("com.example.counter.shared_types", output_root.join("java"))?;
 
-    gen.typescript("shared_types", output_root.join("typescript"))
-        .expect("typescript type gen failed");
+    gen.typescript("shared_types", output_root.join("typescript"))?;
+
+    Ok(())
 }
diff --git a/examples/notes/shared_types/build.rs b/examples/notes/shared_types/build.rs
index 963dcb4b..37d8996c 100644
--- a/examples/notes/shared_types/build.rs
+++ b/examples/notes/shared_types/build.rs
@@ -2,27 +2,26 @@ use crux_core::typegen::TypeGen;
 use shared::{NoteEditor, TextCursor};
 use std::path::PathBuf;
 
-fn main() {
+fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=../shared");
 
     let mut gen = TypeGen::new();
 
-    gen.register_app::<NoteEditor>().expect("register");
+    gen.register_app::<NoteEditor>()?;
 
     // Note: currently required as we can't find enums inside enums, see:
     // https://github.com/zefchain/serde-reflection/tree/main/serde-reflection#supported-features
-    gen.register_type::<TextCursor>().expect("register");
+    gen.register_type::<TextCursor>()?;
 
     let output_root = PathBuf::from("./generated");
 
-    gen.swift("SharedTypes", output_root.join("swift"))
-        .expect("swift type gen failed");
+    gen.swift("SharedTypes", output_root.join("swift"))?;
 
     // TODO these are for later
     //
-    // gen.java("com.example.counter.shared_types", output_root.join("java"))
-    //     .expect("java type gen failed");
+    // gen.java("com.example.counter.shared_types", output_root.join("java"))?;
 
-    gen.typescript("shared_types", output_root.join("typescript"))
-        .expect("typescript type gen failed");
+    gen.typescript("shared_types", output_root.join("typescript"))?;
+
+    Ok(())
 }
diff --git a/examples/simple_counter/shared_types/build.rs b/examples/simple_counter/shared_types/build.rs
index cc6f532b..c3187c1a 100644
--- a/examples/simple_counter/shared_types/build.rs
+++ b/examples/simple_counter/shared_types/build.rs
@@ -2,24 +2,23 @@ use crux_core::typegen::TypeGen;
 use shared::Counter;
 use std::path::PathBuf;
 
-fn main() {
+fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=../shared");
 
     let mut gen = TypeGen::new();
 
-    gen.register_app::<Counter>().expect("register");
+    gen.register_app::<Counter>()?;
 
     let output_root = PathBuf::from("./generated");
 
-    gen.swift("SharedTypes", output_root.join("swift"))
-        .expect("swift type gen failed");
+    gen.swift("SharedTypes", output_root.join("swift"))?;
 
     gen.java(
         "com.example.simple_counter.shared_types",
         output_root.join("java"),
-    )
-    .expect("java type gen failed");
+    )?;
 
-    gen.typescript("shared_types", output_root.join("typescript"))
-        .expect("typescript type gen failed");
+    gen.typescript("shared_types", output_root.join("typescript"))?;
+
+    Ok(())
 }
diff --git a/examples/tap_to_pay/shared/src/app.rs b/examples/tap_to_pay/shared/src/app.rs
index 5ea0b1bc..437d16df 100644
--- a/examples/tap_to_pay/shared/src/app.rs
+++ b/examples/tap_to_pay/shared/src/app.rs
@@ -38,6 +38,7 @@ pub enum Screen {
 
 #[derive(Effect)]
 #[effect(app = "App")]
+#[cfg_attr(feature = "typegen", derive(crux_macros::Export))]
 pub struct Capabilities {
     render: Render<Event>,
     delay: Delay<Event>,
diff --git a/examples/tap_to_pay/shared_types/build.rs b/examples/tap_to_pay/shared_types/build.rs
index c505cd10..d549dafd 100644
--- a/examples/tap_to_pay/shared_types/build.rs
+++ b/examples/tap_to_pay/shared_types/build.rs
@@ -1,36 +1,20 @@
-use anyhow::Result;
-use crux_core::{bridge::Request, typegen::TypeGen};
-use crux_http::protocol::{HttpRequest, HttpResponse};
-use shared::{EffectFfi, Event, Payment, PaymentStatus, Receipt, ReceiptStatus, ViewModel};
+use crux_core::typegen::TypeGen;
+use shared::{App, PaymentStatus, ReceiptStatus};
 use std::path::PathBuf;
 
-fn main() {
+fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=../shared");
 
     let mut gen = TypeGen::new();
 
-    register_types(&mut gen).expect("type registration failed");
+    gen.register_app::<App>()?;
 
-    let output_root = PathBuf::from("./generated");
-
-    gen.swift("SharedTypes", output_root.join("swift"))
-        .expect("swift type gen failed");
-}
-
-fn register_types(gen: &mut TypeGen) -> Result<()> {
-    gen.register_type::<Request<EffectFfi>>()?;
-
-    gen.register_type::<EffectFfi>()?;
-    gen.register_type::<HttpRequest>()?;
-
-    gen.register_type::<Event>()?;
-    gen.register_type::<HttpResponse>()?;
-
-    gen.register_type::<ViewModel>()?;
-    gen.register_type::<Payment>()?;
     gen.register_type::<PaymentStatus>()?;
-    gen.register_type::<Receipt>()?;
     gen.register_type::<ReceiptStatus>()?;
 
+    let output_root = PathBuf::from("./generated");
+
+    gen.swift("SharedTypes", output_root.join("swift"))?;
+
     Ok(())
 }
diff --git a/scripts/update_deps.sh b/scripts/update_deps.sh
index 786574eb..18495d1d 100755
--- a/scripts/update_deps.sh
+++ b/scripts/update_deps.sh
@@ -1,6 +1,6 @@
 #!/opt/homebrew/bin/bash
 
-set -e
+set -euo pipefail
 
 # until https://github.com/killercup/cargo-edit/pull/870 is merged
 CARGO_REGISTRIES_CRATES_IO_PROTOCOL=git cargo fetch
diff --git a/templates/simple_counter/shared_types/build.rs b/templates/simple_counter/shared_types/build.rs
index 262c1e29..1f3421d4 100644
--- a/templates/simple_counter/shared_types/build.rs
+++ b/templates/simple_counter/shared_types/build.rs
@@ -2,24 +2,23 @@ use crux_core::typegen::TypeGen;
 use {{core_name}}::Counter;
 use std::path::PathBuf;
 
-fn main() {
+fn main() -> anyhow::Result<()> {
     println!("cargo:rerun-if-changed=../shared");
 
     let mut gen = TypeGen::new();
 
-    gen.register_app::<Counter>().expect("register");
+    gen.register_app::<Counter>()?;
 
     let output_root = PathBuf::from("./generated");
 
-    gen.swift("SharedTypes", output_root.join("swift"))
-        .expect("swift type gen failed");
+    gen.swift("SharedTypes", output_root.join("swift"))?;
 
     gen.java(
         "com.example.simple_counter.shared_types",
         output_root.join("java"),
-    )
-    .expect("java type gen failed");
+    )?;
 
-    gen.typescript("shared_types", output_root.join("typescript"))
-        .expect("typescript type gen failed");
+    gen.typescript("shared_types", output_root.join("typescript"))?;
+
+    Ok(())
 }


Test patch:
diff --git a/scripts/build_and_test.sh b/scripts/build_and_test.sh
new file mode 100755
index 00000000..0e1480a0
--- /dev/null
+++ b/scripts/build_and_test.sh
@@ -0,0 +1,26 @@
+#!/opt/homebrew/bin/bash
+
+set -euo pipefail
+
+mapfile -t all < <(fd Cargo.toml)
+roots=()
+for file in "${all[@]}"; do
+  if [[ $file == *"templates"* ]]; then
+    continue
+  fi
+  roots+=("$(
+    cd "$(dirname "$file")"
+    cargo metadata --format-version 1 | jq -r '.workspace_root'
+  )")
+done
+
+readarray -t sorted < <(printf '%s\n' "${roots[@]}" | sort -u)
+
+for dir in "${sorted[@]}"; do
+  (
+    cd "$dir"
+    cargo fmt --all --check
+    cargo build --all-features
+    cargo nextest run --all-features
+  )
+done
diff --git a/scripts/build_and_test_examples.sh b/scripts/build_and_test_examples.sh
deleted file mode 100755
index 2312172b..00000000
--- a/scripts/build_and_test_examples.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-set -euxo pipefail
-
-for dir in ./examples/*; do
-  (
-    cd "$dir"
-    cargo fmt --all --check
-    cargo build --all-features
-    cargo nextest run --all-features
-  )
-done
diff --git a/scripts/doc_tests.sh b/scripts/doc_tests.sh
new file mode 100755
index 00000000..65fc5e4a
--- /dev/null
+++ b/scripts/doc_tests.sh
@@ -0,0 +1,5 @@
+#!/opt/homebrew/bin/bash
+
+set -euo pipefail
+
+cargo test --doc --all-features