From 3166f2110f551effccbf9e21aced9732522db795 Mon Sep 17 00:00:00 2001 From: Marc-Antoine ARNAUD Date: Thu, 19 Feb 2026 11:58:22 +0100 Subject: [PATCH 01/12] feat: add node-package configuration --- .github/workflows/ci.yaml | 1 + Cargo.lock | 267 +++++++++++++++++- Cargo.toml | 5 + build.rs | 5 + examples/node-module/Cargo.lock | 146 ++++++++++ examples/node-module/Cargo.toml | 10 + examples/node-module/Trunk.toml | 3 + examples/node-module/index.html | 14 + examples/node-module/src/main.rs | 34 +++ examples/yaml-config/Cargo.lock | 4 +- src/config/models/hook.rs | 16 +- src/config/models/mod.rs | 6 + src/config/models/node_package.rs | 27 ++ src/config/rt/build.rs | 6 + src/main.rs | 5 + src/node_packages.rs | 94 ++++++ src/node_packages/get_package_error.rs | 11 + src/node_packages/node_package_client.rs | 77 +++++ src/node_packages/node_package_information.rs | 16 ++ src/pipelines/html.rs | 3 + src/pipelines/mod.rs | 26 +- src/pipelines/rust/mod.rs | 19 +- src/serve/mod.rs | 9 +- src/version/enabled/state.rs | 16 +- src/ws.rs | 8 +- 25 files changed, 778 insertions(+), 50 deletions(-) create mode 100644 build.rs create mode 100644 examples/node-module/Cargo.lock create mode 100644 examples/node-module/Cargo.toml create mode 100644 examples/node-module/Trunk.toml create mode 100644 examples/node-module/index.html create mode 100644 examples/node-module/src/main.rs create mode 100644 src/config/models/node_package.rs create mode 100644 src/node_packages.rs create mode 100644 src/node_packages/get_package_error.rs create mode 100644 src/node_packages/node_package_client.rs create mode 100644 src/node_packages/node_package_information.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 80d851f4..2bf3cbe1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -171,6 +171,7 @@ jobs: - initializer - leptos - no-rust + - node-module - proxy - seed - target-path diff --git a/Cargo.lock b/Cargo.lock index 86f0604c..38e9e07b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler2" @@ -144,6 +144,133 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-tar" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9933aa2da420042c67e2ea83eec765919347d9742592744dc97cc42ef20c5d" +dependencies = [ + "async-std", + "filetime", + "futures-core", + "libc", + "redox_syscall 0.7.0", + "xattr", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.89" @@ -317,6 +444,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bstr" version = "1.12.1" @@ -327,6 +467,12 @@ dependencies = [ "serde", ] +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + [[package]] name = "bumpalo" version = "3.19.1" @@ -526,6 +672,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" version = "0.16.2" @@ -1033,6 +1188,33 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1199,6 +1381,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -1334,6 +1529,18 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.4.13" @@ -1390,6 +1597,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hickory-proto" version = "0.25.2" @@ -1884,6 +2097,15 @@ dependencies = [ "libc", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2013,6 +2235,9 @@ name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] [[package]] name = "lol_html" @@ -2471,6 +2696,12 @@ dependencies = [ "vlq", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2653,12 +2884,37 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -4170,9 +4426,11 @@ name = "trunk" version = "0.21.14" dependencies = [ "anyhow", + "async-tar", "axum", "axum-server", "base64", + "built", "bytes", "cargo-lock", "cargo_metadata", @@ -4226,6 +4484,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "url", "which", "zip", ] @@ -4368,6 +4627,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 0ec7b917..24feaa4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ panic = "abort" [dependencies] anyhow = "1" +async-tar = "0.6" axum = { version = "0.8.1", features = ["ws"] } axum-server = "0.7" base64 = "0.22" @@ -72,6 +73,7 @@ toml = "0.8" tower-http = { version = "0.6.1", features = ["fs", "trace", "set-header"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +url = "2" which = "8" zip = "7" @@ -88,6 +90,9 @@ openssl = { version = "0.10", default-features = false, optional = true } tempfile = "3" rstest = "0.26.1" +[build-dependencies] +built = "0.8" + [features] default = ["update_check", "rustls"] diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..3800c173 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +extern crate built; + +fn main() { + built::write_built_file().expect("Failed to acquire build-time information"); +} diff --git a/examples/node-module/Cargo.lock b/examples/node-module/Cargo.lock new file mode 100644 index 00000000..7ad23674 --- /dev/null +++ b/examples/node-module/Cargo.lock @@ -0,0 +1,146 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bumpalo" +version = "3.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6f81257d10a0f602a294ae4182251151ff97dbb504ef9afcdda4a64b24d9b4" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "node-module-example" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "syn" +version = "2.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/examples/node-module/Cargo.toml b/examples/node-module/Cargo.toml new file mode 100644 index 00000000..f5c56e6a --- /dev/null +++ b/examples/node-module/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "node-module-example" +version = "0.1.0" +authors = ["Marc-Antoine Arnaud "] +edition = "2024" + +[dependencies] +console_error_panic_hook = "0.1" +wasm-bindgen = "0.2" +web-sys = { version = "0.3", features = ["Window", "Document", "HtmlElement", "Node", "Text"] } diff --git a/examples/node-module/Trunk.toml b/examples/node-module/Trunk.toml new file mode 100644 index 00000000..dd87af58 --- /dev/null +++ b/examples/node-module/Trunk.toml @@ -0,0 +1,3 @@ +[[node_packages]] +name = "@patternfly/patternfly" +version = "6.4.0" diff --git a/examples/node-module/index.html b/examples/node-module/index.html new file mode 100644 index 00000000..05730e0c --- /dev/null +++ b/examples/node-module/index.html @@ -0,0 +1,14 @@ + + + + + + Npm dependency + + + + + + + + diff --git a/examples/node-module/src/main.rs b/examples/node-module/src/main.rs new file mode 100644 index 00000000..975109b0 --- /dev/null +++ b/examples/node-module/src/main.rs @@ -0,0 +1,34 @@ +#![recursion_limit = "1024"] + +use console_error_panic_hook::set_once as set_panic_hook; +use web_sys::window; + +fn start_app() { + let document = window() + .and_then(|win| win.document()) + .expect("could not access document"); + let body = document.body().expect("could not access document.body"); + + let button_text_node = document.create_element("span").unwrap(); + button_text_node + .set_attribute("class", "pf-v6-c-button__text") + .unwrap(); + button_text_node.set_text_content(Some("Primary")); + + let button_node = document.create_element("button").unwrap(); + button_node + .set_attribute("class", "pf-v6-c-button pf-m-primary") + .unwrap(); + button_node.set_attribute("type", "button").unwrap(); + button_node + .append_child(button_text_node.as_ref()) + .expect("failed to append button text"); + + body.append_child(button_node.as_ref()) + .expect("failed to append button"); +} + +fn main() { + set_panic_hook(); + start_app(); +} diff --git a/examples/yaml-config/Cargo.lock b/examples/yaml-config/Cargo.lock index e014da59..358edfbd 100644 --- a/examples/yaml-config/Cargo.lock +++ b/examples/yaml-config/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "bumpalo" @@ -145,7 +145,7 @@ dependencies = [ ] [[package]] -name = "yaml-exmaple" +name = "yaml-example" version = "0.1.0" dependencies = [ "console_error_panic_hook", diff --git a/src/config/models/hook.rs b/src/config/models/hook.rs index d912b6e6..f1c9effa 100644 --- a/src/config/models/hook.rs +++ b/src/config/models/hook.rs @@ -29,10 +29,10 @@ impl Hook { if let Some(cfg) = self.overrides.macos.as_ref() { return &cfg.command; } - } else if cfg!(target_os = "linux") { - if let Some(cfg) = self.overrides.linux.as_ref() { - return &cfg.command; - } + } else if cfg!(target_os = "linux") + && let Some(cfg) = self.overrides.linux.as_ref() + { + return &cfg.command; } &self.command @@ -47,10 +47,10 @@ impl Hook { if let Some(cfg) = self.overrides.macos.as_ref() { return &cfg.command_arguments; } - } else if cfg!(target_os = "linux") { - if let Some(cfg) = self.overrides.linux.as_ref() { - return &cfg.command_arguments; - } + } else if cfg!(target_os = "linux") + && let Some(cfg) = self.overrides.linux.as_ref() + { + return &cfg.command_arguments; } &self.command_arguments diff --git a/src/config/models/mod.rs b/src/config/models/mod.rs index a8d74f90..6cee7f72 100644 --- a/src/config/models/mod.rs +++ b/src/config/models/mod.rs @@ -9,6 +9,7 @@ mod build; mod clean; mod core; mod hook; +mod node_package; mod proxy; mod serve; mod tools; @@ -18,6 +19,7 @@ pub use build::*; pub use clean::*; pub use core::*; pub use hook::*; +pub use node_package::*; pub use proxy::*; pub use serve::*; pub use tools::*; @@ -56,6 +58,9 @@ pub struct Configuration { #[serde(default)] pub hooks: Hooks, + #[serde(default)] + pub node_packages: NodePackages, + #[serde(default)] pub watch: Watch, @@ -80,6 +85,7 @@ impl ConfigModel for Configuration { self.tools.migrate()?; self.hooks.migrate()?; + self.node_packages.migrate()?; self.proxies.migrate()?; self.clean.migrate()?; diff --git a/src/config/models/node_package.rs b/src/config/models/node_package.rs new file mode 100644 index 00000000..ddf573a7 --- /dev/null +++ b/src/config/models/node_package.rs @@ -0,0 +1,27 @@ +use crate::config::models::ConfigModel; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Config options for build system node module. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct NodePackage { + /// Optional npm registry to use (default is https://registry.npmjs.org) + #[serde(default)] + pub registry: Option, + /// Package name in https://npmjs.com + pub name: String, + /// Version of the package + pub version: String, + /// Path where to install the package + #[serde(default)] + pub target_path: Option, +} + +/// New type for handling `Vec` +#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct NodePackages( + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub Vec, +); + +impl ConfigModel for NodePackages {} diff --git a/src/config/rt/build.rs b/src/config/rt/build.rs index 5fe8f0f9..8b12fb87 100644 --- a/src/config/rt/build.rs +++ b/src/config/rt/build.rs @@ -1,4 +1,5 @@ use super::{super::STAGE_DIR, RtcBuilder}; +use crate::config::models::{NodePackage, NodePackages}; use crate::{ config::{ Hooks, @@ -60,6 +61,8 @@ pub struct RtcBuild { pub cargo_example: Option, /// Configuration for automatic application download. pub tools: Tools, + /// Build process node_package. + pub node_packages: Vec, /// Build process hooks. pub hooks: Vec, /// A bool indicating if the output HTML should have the WebSocket autoloader injected. @@ -120,6 +123,7 @@ impl RtcBuild { core: core_config, build, tools, + node_packages: NodePackages(node_packages), hooks: Hooks(hooks), .. } = config; @@ -199,6 +203,7 @@ impl RtcBuild { cargo_features, cargo_example: build.example, tools, + node_packages, hooks, inject_autoloader, inject_scripts: build.inject_scripts, @@ -244,6 +249,7 @@ impl RtcBuild { cargo_features: Features::All, cargo_example: None, tools: Default::default(), + node_packages: Vec::new(), hooks: Vec::new(), inject_autoloader: true, inject_scripts: true, diff --git a/src/main.rs b/src/main.rs index f7844f0a..045ca12b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,16 @@ #![deny(clippy::expect_used)] #![deny(clippy::unwrap_used)] +pub mod built_info { + // The file has been placed there by the build script. + include!(concat!(env!("OUT_DIR"), "/built.rs")); +} mod build; mod cmd; mod common; mod config; mod hooks; +mod node_packages; mod pipelines; mod processing; mod proxy; diff --git a/src/node_packages.rs b/src/node_packages.rs new file mode 100644 index 00000000..78efaf3b --- /dev/null +++ b/src/node_packages.rs @@ -0,0 +1,94 @@ +mod get_package_error; +mod node_package_client; +mod node_package_information; + +use crate::common::copy_dir_recursive; +use crate::config::rt::RtcBuild; +use flate2::read::GzDecoder; +use futures_util::StreamExt; +use futures_util::io::AllowStdIo; +use futures_util::stream::FuturesUnordered; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::task::JoinHandle; + +/// A `FuturesUnordered` containing a `JoinHandle` for each hook-running task. +pub type NodePackageHandles = FuturesUnordered>>; +pub fn spawn_node_packages(cfg: Arc) -> NodePackageHandles { + tracing::info!("node packages {:?}", cfg.node_packages); + + let futures: FuturesUnordered<_> = cfg + .node_packages + .iter() + .map(|node_package_cfg| { + let package_information = format!( + "{}@{}{}", + node_package_cfg.name, + node_package_cfg.version, + node_package_cfg + .registry + .clone() + .map(|registry| format!("(registry: {registry})")) + .unwrap_or_default() + ); + + tracing::info!("download node package {package_information}"); + + let node_package_cfg = node_package_cfg.clone(); + + tokio::spawn(async move { + let http_node_module_client = if let Some(registry) = node_package_cfg.registry { + node_package_client::NodePackageClient::new(®istry)? + } else { + node_package_client::NodePackageClient::default() + }; + + let target_path = node_package_cfg.target_path.unwrap_or(format!( + "target/node_modules/{}/{}", + node_package_cfg.name, node_package_cfg.version + )); + let target_path = PathBuf::from(target_path); + + if !target_path.exists() + && let Ok(package) = http_node_module_client + .get_package(&node_package_cfg.name, &node_package_cfg.version) + .await + { + let tarball = reqwest::get(package.distribution.tarball) + .await? + .bytes() + .await?; + + let archive_data = + AllowStdIo::new(GzDecoder::new(std::io::Cursor::new(tarball))); + + let archive = async_tar::Archive::new(archive_data); + + archive.unpack(&target_path).await?; + + let package_directory = target_path.join("package"); + + tracing::debug!("move from {package_directory:?} to {target_path:?}"); + + copy_dir_recursive(package_directory.clone(), target_path).await?; + std::fs::remove_dir_all(package_directory)?; + + tracing::info!("finished to download node package {package_information}"); + } + + Ok(()) + }) + }) + .collect(); + + futures +} + +/// Waits for all the given hooks to finish. +pub async fn wait_node_packages(mut futures: NodePackageHandles) -> anyhow::Result<()> { + while let Some(result) = futures.next().await { + result??; + } + + Ok(()) +} diff --git a/src/node_packages/get_package_error.rs b/src/node_packages/get_package_error.rs new file mode 100644 index 00000000..b2b115e8 --- /dev/null +++ b/src/node_packages/get_package_error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Error)] +pub enum GetPackageError { + #[error("failed to send `GetPackage` request: {0}")] + Send(String), + #[error("failed to receive `GetPackage` response: {0}")] + Receive(String), + #[error("node package not found")] + NotFound, +} diff --git a/src/node_packages/node_package_client.rs b/src/node_packages/node_package_client.rs new file mode 100644 index 00000000..f746e0be --- /dev/null +++ b/src/node_packages/node_package_client.rs @@ -0,0 +1,77 @@ +use super::get_package_error::GetPackageError; +use super::node_package_information::NodePackageInformation; +use crate::version::VERSION; +use anyhow::Result; +use reqwest::{Client, StatusCode}; +use url::Url; + +pub struct NodePackageClient { + client: Client, + server: Url, +} + +impl NodePackageClient { + pub fn new(url: &str) -> Result { + let client = Client::builder() + .user_agent(format!("{}/{VERSION}", env!("CARGO_PKG_NAME"))) + .build()?; + + let server = Url::parse(url).map_err(|error| { + std::io::Error::other(format!("the npm registry URL is well-formed: {error}")) + })?; + + Ok(Self { client, server }) + } + + pub fn api_url(&self, segments: I) -> Url + where + I: IntoIterator, + I::Item: AsRef, + { + let mut res = self.server.clone(); + + if let Ok(mut p) = res.path_segments_mut() { + p.extend(segments); + } + + res + } +} + +impl Default for NodePackageClient { + #![allow(clippy::unwrap_used)] + fn default() -> Self { + Self::new("https://registry.npmjs.org/").unwrap() + } +} + +impl NodePackageClient { + pub async fn get_package( + &self, + package_name: &str, + package_version: &str, + ) -> Result { + let url = self.api_url([package_name, package_version]); + + let res = self + .client + .get(url) + .send() + .await + .map_err(|e| GetPackageError::Send(format!("{e:?}")))?; + + match res.status() { + StatusCode::OK => { + let body: NodePackageInformation = res + .json() + .await + .map_err(|e| GetPackageError::Receive(format!("{e:?}")))?; + Ok(body) + } + StatusCode::NOT_FOUND => Err(GetPackageError::NotFound), + code => Err(GetPackageError::Receive(format!( + "unexpected status code {code}" + ))), + } + } +} diff --git a/src/node_packages/node_package_information.rs b/src/node_packages/node_package_information.rs new file mode 100644 index 00000000..5c4ac8e8 --- /dev/null +++ b/src/node_packages/node_package_information.rs @@ -0,0 +1,16 @@ +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] +pub struct NodePackageInformation { + pub name: String, + pub version: String, + #[serde(rename = "dist")] + pub distribution: NodePackageDistribution, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] +pub struct NodePackageDistribution { + pub shasum: String, + pub tarball: String, + #[serde(rename = "fileCount")] + pub file_count: usize, + pub integrity: String, +} diff --git a/src/pipelines/html.rs b/src/pipelines/html.rs index 9fa35142..f3661030 100644 --- a/src/pipelines/html.rs +++ b/src/pipelines/html.rs @@ -1,5 +1,6 @@ //! Source HTML pipelines. +use crate::node_packages::{spawn_node_packages, wait_node_packages}; use crate::{ common::{ html_rewrite::{Document, DocumentOptions}, @@ -82,6 +83,8 @@ impl HtmlPipeline { async fn run(self: Arc) -> Result<()> { tracing::debug!("spawning asset pipelines"); + wait_node_packages(spawn_node_packages(self.cfg.clone())).await?; + // Spawn and wait on pre-build hooks. wait_hooks(spawn_hooks(self.cfg.clone(), PipelineStage::PreBuild)).await?; diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs index 96461b82..e88653ce 100644 --- a/src/pipelines/mod.rs +++ b/src/pipelines/mod.rs @@ -172,18 +172,20 @@ impl TrunkAsset { /// Spawn the build pipeline for this asset. pub fn spawn(self) -> JoinHandle> { - match self { - Self::Css(inner) => inner.spawn(), - Self::Sass(inner) => inner.spawn(), - Self::TailwindCss(inner) => inner.spawn(), - Self::TailwindCssExtra(inner) => inner.spawn(), - Self::Js(inner) => inner.spawn(), - Self::Icon(inner) => inner.spawn(), - Self::Inline(inner) => inner.spawn(), - Self::CopyFile(inner) => inner.spawn(), - Self::CopyDir(inner) => inner.spawn(), - Self::RustApp(inner) => inner.spawn(), - } + let (join_handle, a) = match self { + Self::Css(inner) => (inner.spawn(), "Css"), + Self::Sass(inner) => (inner.spawn(), "Sass"), + Self::TailwindCss(inner) => (inner.spawn(), "TailwindCss"), + Self::TailwindCssExtra(inner) => (inner.spawn(), "TailwindCssExtra"), + Self::Js(inner) => (inner.spawn(), "Js"), + Self::Icon(inner) => (inner.spawn(), "Icon"), + Self::Inline(inner) => (inner.spawn(), "Inline"), + Self::CopyFile(inner) => (inner.spawn(), "CopyFile"), + Self::CopyDir(inner) => (inner.spawn(), "CopyDir"), + Self::RustApp(inner) => (inner.spawn(), "RustApp"), + }; + tracing::warn!("spawning pipeline for {a:?}"); + join_handle } } diff --git a/src/pipelines/rust/mod.rs b/src/pipelines/rust/mod.rs index 2fb1c4aa..8a00e383 100644 --- a/src/pipelines/rust/mod.rs +++ b/src/pipelines/rust/mod.rs @@ -457,18 +457,17 @@ impl RustApp { // Send cargo's target dir over to the watcher to be ignored. We must do this before // checking for errors, otherwise the dir will never be ignored. If we attempt to do // this pre-build, the canonicalization will fail and will not be ignored. - if let Some(chan) = &mut self.ignore_chan { - if let Ok(target_dir) = self + if let Some(chan) = &mut self.ignore_chan + && let Ok(target_dir) = self .manifest .metadata .target_directory .clone() .into_std_path_buf() .canonicalize() - { - let target_dir_recursive = target_dir.join("**"); - let _ = chan.try_send(vec![target_dir, target_dir_recursive]); - } + { + let target_dir_recursive = target_dir.join("**"); + let _ = chan.try_send(vec![target_dir, target_dir_recursive]); } // Now propagate any errors which came from the cargo build. @@ -858,10 +857,10 @@ impl RustApp { } // if we have a target name - if let Some(target_name) = &self.target_name { - if target_name != &art.target.name { - return false; - } + if let Some(target_name) = &self.target_name + && target_name != &art.target.name + { + return false; } true diff --git a/src/serve/mod.rs b/src/serve/mod.rs index 55f65294..31c59987 100644 --- a/src/serve/mod.rs +++ b/src/serve/mod.rs @@ -98,10 +98,11 @@ impl ServeSystem { .await?; // Open the browser. - if self.cfg.open && build_res.is_ok() { - if let Err(err) = open::that(self.open_http_addr) { - tracing::error!(error = ?err, "error opening browser"); - } + if self.cfg.open + && build_res.is_ok() + && let Err(err) = open::that(self.open_http_addr) + { + tracing::error!(error = ?err, "error opening browser"); } drop(self.shutdown_tx); // Drop the broadcast channel to ensure it does not keep the system alive. diff --git a/src/version/enabled/state.rs b/src/version/enabled/state.rs index a8b70e2a..deb7f24a 100644 --- a/src/version/enabled/state.rs +++ b/src/version/enabled/state.rs @@ -96,14 +96,14 @@ pub async fn record_checked(versions: Versions) { } }; - if let Some(parent) = file.parent() { - if let Err(err) = tokio::fs::create_dir_all(parent).await { - tracing::debug!( - "Failed to create parent directory for update state ({}): {err}", - parent.display() - ); - return; - } + if let Some(parent) = file.parent() + && let Err(err) = tokio::fs::create_dir_all(parent).await + { + tracing::debug!( + "Failed to create parent directory for update state ({}): {err}", + parent.display() + ); + return; } if let Err(err) = tokio::fs::write(&file, state).await { diff --git a/src/ws.rs b/src/ws.rs index 722355ed..7ab213b3 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -82,14 +82,12 @@ pub(crate) async fn handle_ws(mut ws: WebSocket, state: Arc) { tracing::trace!("Message to send: {msg:?}"); - if let Some(msg) = msg { - if let Ok(text) = serde_json::to_string(&msg) { - if let Err(err) = ws.send(Message::Text(text.into())).await { + if let Some(msg) = msg + && let Ok(text) = serde_json::to_string(&msg) + && let Err(err) = ws.send(Message::Text(text.into())).await { tracing::info!("autoload websocket failed to send: {err}"); break; } - } - } } } } From 229ab9ba5cf41260b95bfda70d9403a55506ab78 Mon Sep 17 00:00:00 2001 From: Marc-Antoine ARNAUD Date: Tue, 24 Feb 2026 13:53:20 +0100 Subject: [PATCH 02/12] feat: set minimal rust version to 1.90.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 24feaa4b..e6753143 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/trunk-rs/trunk" readme = "README.md" categories = ["command-line-utilities", "wasm", "web-programming"] keywords = ["wasm", "bundler", "web", "build-tool", "compiler"] -rust-version = "1.88.0" +rust-version = "1.90.0" [profile.release] lto = "fat" From f33ba8e3a8754ba8808ebb0fcb307602e3c78b84 Mon Sep 17 00:00:00 2001 From: Marc-Antoine ARNAUD Date: Tue, 24 Feb 2026 13:55:54 +0100 Subject: [PATCH 03/12] chore: bump version to 0.22.0-beta.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38e9e07b..55e2e1a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4423,7 +4423,7 @@ dependencies = [ [[package]] name = "trunk" -version = "0.21.14" +version = "0.22.0-beta.1" dependencies = [ "anyhow", "async-tar", diff --git a/Cargo.toml b/Cargo.toml index e6753143..f54f8920 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trunk" -version = "0.21.14" +version = "0.22.0-beta.1" edition = "2024" description = "Build, bundle & ship your Rust WASM application to the web." license = "MIT/Apache-2.0" From 4af14c7a49534a8847c58294fb89b149ab9f3544 Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 27 Feb 2026 08:36:20 +0100 Subject: [PATCH 04/12] ci: align MSRV in CI with Cargo.toml --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2bf3cbe1..9280a3d1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,7 +54,7 @@ jobs: matrix: rust: - stable - - 1.88.0 # MSRV + - 1.90.0 # MSRV steps: - name: Setup | Checkout uses: actions/checkout@v5 From 7c4d66befa6db1f21781670b5e94c71fda2d6b3f Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 27 Feb 2026 08:39:36 +0100 Subject: [PATCH 05/12] chore: relocate node_packages under pipelines --- src/{ => pipelines}/node_packages/get_package_error.rs | 0 src/{node_packages.rs => pipelines/node_packages/mod.rs} | 0 src/{ => pipelines}/node_packages/node_package_client.rs | 0 src/{ => pipelines}/node_packages/node_package_information.rs | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => pipelines}/node_packages/get_package_error.rs (100%) rename src/{node_packages.rs => pipelines/node_packages/mod.rs} (100%) rename src/{ => pipelines}/node_packages/node_package_client.rs (100%) rename src/{ => pipelines}/node_packages/node_package_information.rs (100%) diff --git a/src/node_packages/get_package_error.rs b/src/pipelines/node_packages/get_package_error.rs similarity index 100% rename from src/node_packages/get_package_error.rs rename to src/pipelines/node_packages/get_package_error.rs diff --git a/src/node_packages.rs b/src/pipelines/node_packages/mod.rs similarity index 100% rename from src/node_packages.rs rename to src/pipelines/node_packages/mod.rs diff --git a/src/node_packages/node_package_client.rs b/src/pipelines/node_packages/node_package_client.rs similarity index 100% rename from src/node_packages/node_package_client.rs rename to src/pipelines/node_packages/node_package_client.rs diff --git a/src/node_packages/node_package_information.rs b/src/pipelines/node_packages/node_package_information.rs similarity index 100% rename from src/node_packages/node_package_information.rs rename to src/pipelines/node_packages/node_package_information.rs From 9869ef459ed57eeffe6c239e51294279d8a82e28 Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 27 Feb 2026 08:46:08 +0100 Subject: [PATCH 06/12] chore: refactor user agent handling --- src/main.rs | 1 - src/pipelines/html.rs | 2 +- src/pipelines/mod.rs | 1 + src/pipelines/node_packages/mod.rs | 10 +++------- src/pipelines/node_packages/node_package_client.rs | 10 ++++------ src/version/enabled/mod.rs | 5 ++--- src/version/mod.rs | 4 +++- 7 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 045ca12b..b5421b1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,6 @@ mod cmd; mod common; mod config; mod hooks; -mod node_packages; mod pipelines; mod processing; mod proxy; diff --git a/src/pipelines/html.rs b/src/pipelines/html.rs index f3661030..84190c8c 100644 --- a/src/pipelines/html.rs +++ b/src/pipelines/html.rs @@ -1,6 +1,6 @@ //! Source HTML pipelines. -use crate::node_packages::{spawn_node_packages, wait_node_packages}; +use super::node_packages::{spawn_node_packages, wait_node_packages}; use crate::{ common::{ html_rewrite::{Document, DocumentOptions}, diff --git a/src/pipelines/mod.rs b/src/pipelines/mod.rs index e88653ce..e192d84b 100644 --- a/src/pipelines/mod.rs +++ b/src/pipelines/mod.rs @@ -9,6 +9,7 @@ mod html; mod icon; mod inline; mod js; +mod node_packages; mod rust; mod sass; mod tailwind_css; diff --git a/src/pipelines/node_packages/mod.rs b/src/pipelines/node_packages/mod.rs index 78efaf3b..c7de95f7 100644 --- a/src/pipelines/node_packages/mod.rs +++ b/src/pipelines/node_packages/mod.rs @@ -2,14 +2,10 @@ mod get_package_error; mod node_package_client; mod node_package_information; -use crate::common::copy_dir_recursive; -use crate::config::rt::RtcBuild; +use crate::{common::copy_dir_recursive, config::rt::RtcBuild}; use flate2::read::GzDecoder; -use futures_util::StreamExt; -use futures_util::io::AllowStdIo; -use futures_util::stream::FuturesUnordered; -use std::path::PathBuf; -use std::sync::Arc; +use futures_util::{StreamExt, io::AllowStdIo, stream::FuturesUnordered}; +use std::{path::PathBuf, sync::Arc}; use tokio::task::JoinHandle; /// A `FuturesUnordered` containing a `JoinHandle` for each hook-running task. diff --git a/src/pipelines/node_packages/node_package_client.rs b/src/pipelines/node_packages/node_package_client.rs index f746e0be..d37fd586 100644 --- a/src/pipelines/node_packages/node_package_client.rs +++ b/src/pipelines/node_packages/node_package_client.rs @@ -1,10 +1,10 @@ -use super::get_package_error::GetPackageError; -use super::node_package_information::NodePackageInformation; -use crate::version::VERSION; +use super::{get_package_error::GetPackageError, node_package_information::NodePackageInformation}; +use crate::version::USER_AGENT; use anyhow::Result; use reqwest::{Client, StatusCode}; use url::Url; +/// A client for npmjs.org compatible registries pub struct NodePackageClient { client: Client, server: Url, @@ -12,9 +12,7 @@ pub struct NodePackageClient { impl NodePackageClient { pub fn new(url: &str) -> Result { - let client = Client::builder() - .user_agent(format!("{}/{VERSION}", env!("CARGO_PKG_NAME"))) - .build()?; + let client = Client::builder().user_agent(USER_AGENT).build()?; let server = Url::parse(url).map_err(|error| { std::io::Error::other(format!("the npm registry URL is well-formed: {error}")) diff --git a/src/version/enabled/mod.rs b/src/version/enabled/mod.rs index 6a340e05..9e63e92b 100644 --- a/src/version/enabled/mod.rs +++ b/src/version/enabled/mod.rs @@ -1,5 +1,5 @@ use crate::version::{ - NAME, VERSION, + NAME, USER_AGENT, VERSION, enabled::state::{State, Versions}, }; use semver::Version; @@ -83,8 +83,7 @@ fn announce_version(versions: &Versions) { async fn most_recent() -> anyhow::Result { tracing::debug!("Checking for updates"); - let client = - crates_io_api::AsyncClient::new(&format!("{NAME}/{VERSION}"), Duration::from_secs(1))?; + let client = crates_io_api::AsyncClient::new(USER_AGENT, Duration::from_secs(1))?; let response = client.get_crate(NAME).await?; let versions = response diff --git a/src/version/mod.rs b/src/version/mod.rs index 136123a1..24bfd59a 100644 --- a/src/version/mod.rs +++ b/src/version/mod.rs @@ -14,4 +14,6 @@ pub(crate) use enforce::enforce_version_with; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg(feature = "update_check")] -const NAME: &str = env!("CARGO_PKG_NAME"); +pub const NAME: &str = env!("CARGO_PKG_NAME"); + +pub const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); From e9fa4fb2c72a445ce4095c22db469abbc2ce5bf0 Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 27 Feb 2026 08:47:24 +0100 Subject: [PATCH 07/12] chore: use expect with an short note --- src/pipelines/node_packages/node_package_client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pipelines/node_packages/node_package_client.rs b/src/pipelines/node_packages/node_package_client.rs index d37fd586..43f08a09 100644 --- a/src/pipelines/node_packages/node_package_client.rs +++ b/src/pipelines/node_packages/node_package_client.rs @@ -37,9 +37,9 @@ impl NodePackageClient { } impl Default for NodePackageClient { - #![allow(clippy::unwrap_used)] + #![allow(clippy::expect_used)] fn default() -> Self { - Self::new("https://registry.npmjs.org/").unwrap() + Self::new("https://registry.npmjs.org/").expect("The NPMJS URL must parse") } } From cad84bbdcbcee971400380beed3e1aaad7fdb3b9 Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 27 Feb 2026 08:48:15 +0100 Subject: [PATCH 08/12] build: drop the build script --- Cargo.lock | 7 ------- Cargo.toml | 3 --- build.rs | 5 ----- src/main.rs | 4 ---- 4 files changed, 19 deletions(-) delete mode 100644 build.rs diff --git a/Cargo.lock b/Cargo.lock index 55e2e1a7..41ef00e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -467,12 +467,6 @@ dependencies = [ "serde", ] -[[package]] -name = "built" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" - [[package]] name = "bumpalo" version = "3.19.1" @@ -4430,7 +4424,6 @@ dependencies = [ "axum", "axum-server", "base64", - "built", "bytes", "cargo-lock", "cargo_metadata", diff --git a/Cargo.toml b/Cargo.toml index f54f8920..8392a026 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,9 +90,6 @@ openssl = { version = "0.10", default-features = false, optional = true } tempfile = "3" rstest = "0.26.1" -[build-dependencies] -built = "0.8" - [features] default = ["update_check", "rustls"] diff --git a/build.rs b/build.rs deleted file mode 100644 index 3800c173..00000000 --- a/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate built; - -fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); -} diff --git a/src/main.rs b/src/main.rs index b5421b1c..f7844f0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,6 @@ #![deny(clippy::expect_used)] #![deny(clippy::unwrap_used)] -pub mod built_info { - // The file has been placed there by the build script. - include!(concat!(env!("OUT_DIR"), "/built.rs")); -} mod build; mod cmd; mod common; From 83ba0311c6557cb6faec7e42f7cbabcdc679b2c9 Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 27 Feb 2026 09:02:30 +0100 Subject: [PATCH 09/12] chore: stream file directly into decoding --- Cargo.lock | 255 +++-------------------------- Cargo.toml | 4 +- src/pipelines/node_packages/mod.rs | 23 +-- 3 files changed, 38 insertions(+), 244 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41ef00e2..e038549b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,110 +145,15 @@ dependencies = [ ] [[package]] -name = "async-channel" -version = "1.9.0" +name = "async-compression" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", + "compression-codecs", + "compression-core", "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener 5.4.1", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", + "tokio", ] [[package]] @@ -257,20 +162,14 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9933aa2da420042c67e2ea83eec765919347d9742592744dc97cc42ef20c5d" dependencies = [ - "async-std", "filetime", "futures-core", "libc", "redox_syscall 0.7.0", - "xattr", + "tokio", + "tokio-stream", ] -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" version = "0.1.89" @@ -444,19 +343,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel 2.5.0", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - [[package]] name = "bstr" version = "1.12.1" @@ -667,14 +553,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "compression-codecs" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ - "crossbeam-utils", + "compression-core", + "flate2", + "memchr", ] +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "console" version = "0.16.2" @@ -1182,33 +1076,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener 5.4.1", - "pin-project-lite", -] - [[package]] name = "fastrand" version = "2.3.0" @@ -1375,19 +1242,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.31" @@ -1523,18 +1377,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" version = "0.4.13" @@ -1591,12 +1433,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hickory-proto" version = "0.25.2" @@ -2091,15 +1927,6 @@ dependencies = [ "libc", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -2229,9 +2056,6 @@ name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -dependencies = [ - "value-bag", -] [[package]] name = "lol_html" @@ -2690,12 +2514,6 @@ dependencies = [ "vlq", ] -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - [[package]] name = "parking_lot" version = "0.12.5" @@ -2878,37 +2696,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - [[package]] name = "portable-atomic" version = "1.13.1" @@ -4420,6 +4213,7 @@ name = "trunk" version = "0.22.0-beta.1" dependencies = [ "anyhow", + "async-compression", "async-tar", "axum", "axum-server", @@ -4473,6 +4267,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-tungstenite 0.26.2", + "tokio-util", "toml", "tower-http", "tracing", @@ -4620,12 +4415,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "value-bag" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" - [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 8392a026..5c190300 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ panic = "abort" [dependencies] anyhow = "1" -async-tar = "0.6" +async-compression = { version = "0.4.41", features = ["gzip", "tokio"] } +async-tar = { version = "0.6", default-features = false, features = ["runtime-tokio"] } axum = { version = "0.8.1", features = ["ws"] } axum-server = "0.7" base64 = "0.22" @@ -69,6 +70,7 @@ time = { version = "0.3", features = ["serde-well-known"] } tokio = { version = "1", default-features = false, features = ["full"] } tokio-stream = { version = "0.1", default-features = false, features = ["fs", "sync"] } tokio-tungstenite = "0.26" +tokio-util = "0.7.18" toml = "0.8" tower-http = { version = "0.6.1", features = ["fs", "trace", "set-header"] } tracing = "0.1" diff --git a/src/pipelines/node_packages/mod.rs b/src/pipelines/node_packages/mod.rs index c7de95f7..44856b37 100644 --- a/src/pipelines/node_packages/mod.rs +++ b/src/pipelines/node_packages/mod.rs @@ -2,11 +2,15 @@ mod get_package_error; mod node_package_client; mod node_package_information; -use crate::{common::copy_dir_recursive, config::rt::RtcBuild}; -use flate2::read::GzDecoder; -use futures_util::{StreamExt, io::AllowStdIo, stream::FuturesUnordered}; -use std::{path::PathBuf, sync::Arc}; +use crate::{ + common::copy_dir_recursive, config::rt::RtcBuild, + pipelines::node_packages::node_package_client::NodePackageClient, +}; +use async_compression::tokio::bufread::GzipDecoder; +use futures_util::{StreamExt, TryStreamExt, stream::FuturesUnordered}; +use std::{io, path::PathBuf, sync::Arc}; use tokio::task::JoinHandle; +use tokio_util::io::StreamReader; /// A `FuturesUnordered` containing a `JoinHandle` for each hook-running task. pub type NodePackageHandles = FuturesUnordered>>; @@ -34,9 +38,9 @@ pub fn spawn_node_packages(cfg: Arc) -> NodePackageHandles { tokio::spawn(async move { let http_node_module_client = if let Some(registry) = node_package_cfg.registry { - node_package_client::NodePackageClient::new(®istry)? + NodePackageClient::new(®istry)? } else { - node_package_client::NodePackageClient::default() + NodePackageClient::default() }; let target_path = node_package_cfg.target_path.unwrap_or(format!( @@ -52,11 +56,10 @@ pub fn spawn_node_packages(cfg: Arc) -> NodePackageHandles { { let tarball = reqwest::get(package.distribution.tarball) .await? - .bytes() - .await?; + .bytes_stream(); - let archive_data = - AllowStdIo::new(GzDecoder::new(std::io::Cursor::new(tarball))); + let tarball = tarball.map_err(io::Error::other); + let archive_data = GzipDecoder::new(StreamReader::new(tarball)); let archive = async_tar::Archive::new(archive_data); From 656fbfb5aeacf06508eedaec391590d60bfa169d Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 27 Feb 2026 09:03:25 +0100 Subject: [PATCH 10/12] chore: use async fs operation --- src/pipelines/node_packages/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pipelines/node_packages/mod.rs b/src/pipelines/node_packages/mod.rs index 44856b37..c5ab859f 100644 --- a/src/pipelines/node_packages/mod.rs +++ b/src/pipelines/node_packages/mod.rs @@ -9,7 +9,7 @@ use crate::{ use async_compression::tokio::bufread::GzipDecoder; use futures_util::{StreamExt, TryStreamExt, stream::FuturesUnordered}; use std::{io, path::PathBuf, sync::Arc}; -use tokio::task::JoinHandle; +use tokio::{fs::remove_dir_all, task::JoinHandle}; use tokio_util::io::StreamReader; /// A `FuturesUnordered` containing a `JoinHandle` for each hook-running task. @@ -70,7 +70,7 @@ pub fn spawn_node_packages(cfg: Arc) -> NodePackageHandles { tracing::debug!("move from {package_directory:?} to {target_path:?}"); copy_dir_recursive(package_directory.clone(), target_path).await?; - std::fs::remove_dir_all(package_directory)?; + remove_dir_all(package_directory).await?; tracing::info!("finished to download node package {package_information}"); } From a0cdaa422dbfa57f2da519c3ed63d0656e93ec9b Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 27 Feb 2026 09:09:49 +0100 Subject: [PATCH 11/12] chore: handle errors fetching from node registry --- src/pipelines/node_packages/mod.rs | 52 +++++++++++++++++++----------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/pipelines/node_packages/mod.rs b/src/pipelines/node_packages/mod.rs index c5ab859f..e68ae65e 100644 --- a/src/pipelines/node_packages/mod.rs +++ b/src/pipelines/node_packages/mod.rs @@ -6,6 +6,7 @@ use crate::{ common::copy_dir_recursive, config::rt::RtcBuild, pipelines::node_packages::node_package_client::NodePackageClient, }; +use anyhow::Context; use async_compression::tokio::bufread::GzipDecoder; use futures_util::{StreamExt, TryStreamExt, stream::FuturesUnordered}; use std::{io, path::PathBuf, sync::Arc}; @@ -49,31 +50,46 @@ pub fn spawn_node_packages(cfg: Arc) -> NodePackageHandles { )); let target_path = PathBuf::from(target_path); - if !target_path.exists() - && let Ok(package) = http_node_module_client - .get_package(&node_package_cfg.name, &node_package_cfg.version) - .await - { - let tarball = reqwest::get(package.distribution.tarball) - .await? - .bytes_stream(); + if target_path.exists() { + tracing::debug!( + "target path ({}) already exists, skipping", + target_path.display() + ); + return Ok(()); + } - let tarball = tarball.map_err(io::Error::other); - let archive_data = GzipDecoder::new(StreamReader::new(tarball)); + let package = http_node_module_client + .get_package(&node_package_cfg.name, &node_package_cfg.version) + .await + .with_context(|| { + format!("failed to retrieve Node package: {package_information}") + })?; - let archive = async_tar::Archive::new(archive_data); + // request - archive.unpack(&target_path).await?; + let tarball = reqwest::get(package.distribution.tarball) + .await? + .bytes_stream(); - let package_directory = target_path.join("package"); + // unpack - tracing::debug!("move from {package_directory:?} to {target_path:?}"); + let tarball = tarball.map_err(io::Error::other); + let archive_data = GzipDecoder::new(StreamReader::new(tarball)); + let archive = async_tar::Archive::new(archive_data); + archive.unpack(&target_path).await?; - copy_dir_recursive(package_directory.clone(), target_path).await?; - remove_dir_all(package_directory).await?; + // move - tracing::info!("finished to download node package {package_information}"); - } + let package_directory = target_path.join("package"); + + tracing::debug!("move from {package_directory:?} to {target_path:?}"); + + copy_dir_recursive(package_directory.clone(), target_path).await?; + remove_dir_all(package_directory).await?; + + // done + + tracing::info!("finished to download node package {package_information}"); Ok(()) }) From bb94aaefa2dc7f04fd584beb38c47d8a8c62e2d3 Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 27 Feb 2026 10:08:47 +0100 Subject: [PATCH 12/12] build: enable json due to node package client --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5c190300..1181cf2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ oxipng = "9" rand = "0.9.0" regex = "1" remove_dir_all = "1" -reqwest = { version = "0.12", default-features = false, features = ["stream", "trust-dns"] } +reqwest = { version = "0.12", default-features = false, features = ["stream", "trust-dns", "json"] } schemars = { version = "0.8", features = ["derive"] } seahash = { version = "4", features = ["use_std"] } semver = "1"