From c4dcc52584d53223965f46e8eb657e5062307aea Mon Sep 17 00:00:00 2001
From: zhom <2717306+zhom@users.noreply.github.com>
Date: Wed, 12 Aug 2026 13:28:21 -0700
Subject: [PATCH] refactor: improve performance
---
Cargo.lock | 1460 +++++++++---------
Cargo.toml | 60 +-
README.md | 34 +-
deny.toml | 6 +-
src/bundler.rs | 200 ++-
src/executable.rs | 53 +-
src/node_downloader.rs | 10 +-
src/node_version_manager.rs | 8 +-
src/template/crg.toml | 22 +-
src/template/src/main.rs | 653 ++++----
tests/common/mod.rs | 34 +-
tests/launcher_semantics_integration_test.rs | 218 +++
12 files changed, 1497 insertions(+), 1261 deletions(-)
create mode 100644 tests/launcher_semantics_integration_test.rs
diff --git a/Cargo.lock b/Cargo.lock
index 4375c1d..ecf65c5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,15 +2,6 @@
# It is not intended for manual editing.
version = 4
-[[package]]
-name = "addr2line"
-version = "0.24.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
-dependencies = [
- "gimli",
-]
-
[[package]]
name = "adler2"
version = "2.0.1"
@@ -19,12 +10,12 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aes"
-version = "0.8.4"
+version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58"
dependencies = [
- "cfg-if",
"cipher",
+ "cpubits",
"cpufeatures",
]
@@ -37,26 +28,11 @@ dependencies = [
"memchr",
]
-[[package]]
-name = "android-tzdata"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
-
-[[package]]
-name = "android_system_properties"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "anstream"
-version = "0.6.20"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -69,15 +45,15 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.11"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
-version = "0.2.7"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
@@ -104,18 +80,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.98"
+version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
-
-[[package]]
-name = "arbitrary"
-version = "1.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
-dependencies = [
- "derive_arbitrary",
-]
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "atomic-waker"
@@ -130,18 +97,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
-name = "backtrace"
-version = "0.3.75"
+name = "aws-lc-rs"
+version = "1.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
+checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1"
dependencies = [
- "addr2line",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
- "windows-targets 0.52.6",
+ "aws-lc-sys",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.43.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c"
+dependencies = [
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+ "pkg-config",
]
[[package]]
@@ -149,25 +124,20 @@ name = "banderole"
version = "0.2.2"
dependencies = [
"anyhow",
- "base64",
- "chrono",
"clap",
"console",
"directories",
"env_logger",
- "flate2",
"futures-util",
"indicatif",
"indicatif-log-bridge",
- "lazy_static",
"log",
"lzma-rs",
"reqwest",
"serde",
"serde_json",
"serial_test",
- "sevenz-rust",
- "sha2",
+ "sevenz-rust2",
"tar",
"tempfile",
"tokio",
@@ -183,33 +153,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
-name = "bit-set"
-version = "0.6.0"
+name = "bitflags"
+version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
-dependencies = [
- "bit-vec",
-]
-
-[[package]]
-name = "bit-vec"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.9.1"
+version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "block-buffer"
-version = "0.10.4"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
- "generic-array",
+ "hybrid-array",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
+dependencies = [
+ "hybrid-array",
]
[[package]]
@@ -232,24 +202,40 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "bzip2"
-version = "0.6.0"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff"
+checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
dependencies = [
"libbz2-rs-sys",
]
[[package]]
-name = "cc"
-version = "1.2.32"
+name = "cbc"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
+checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "cc"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
+dependencies = [
+ "find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
[[package]]
name = "cfg-if"
version = "1.0.1"
@@ -257,25 +243,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
-name = "chrono"
-version = "0.4.41"
+name = "cfg_aliases"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "chacha20"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
- "android-tzdata",
- "iana-time-zone",
- "js-sys",
- "num-traits",
- "serde",
- "wasm-bindgen",
- "windows-link",
+ "cfg-if",
+ "cpufeatures",
+ "rand_core",
]
[[package]]
name = "cipher"
-version = "0.4.4"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
dependencies = [
"crypto-common",
"inout",
@@ -283,9 +271,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.43"
+version = "4.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
+checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf"
dependencies = [
"clap_builder",
"clap_derive",
@@ -293,9 +281,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.43"
+version = "4.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
+checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078"
dependencies = [
"anstream",
"anstyle",
@@ -305,21 +293,30 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.41"
+version = "4.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
+checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.3",
]
[[package]]
name = "clap_lex"
-version = "0.7.5"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "cmake"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
+dependencies = [
+ "cc",
+]
[[package]]
name = "colorchoice"
@@ -328,29 +325,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
-name = "console"
-version = "0.16.0"
+name = "combine"
+version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [
- "encode_unicode",
- "libc",
- "once_cell",
- "unicode-width",
- "windows-sys 0.60.2",
+ "bytes",
+ "memchr",
]
[[package]]
-name = "constant_time_eq"
-version = "0.3.1"
+name = "console"
+version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "unicode-width",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "core-foundation"
-version = "0.9.4"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
@@ -363,10 +369,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
-name = "cpufeatures"
-version = "0.2.17"
+name = "cpubits"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
@@ -397,49 +409,53 @@ dependencies = [
[[package]]
name = "crypto-common"
-version = "0.1.6"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
- "generic-array",
- "typenum",
+ "hybrid-array",
]
[[package]]
-name = "deflate64"
-version = "0.1.9"
+name = "defmt"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
-
-[[package]]
-name = "deranged"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
+checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
dependencies = [
- "powerfmt",
+ "bitflags 1.3.2",
+ "defmt-macros",
]
[[package]]
-name = "derive_arbitrary"
-version = "1.4.1"
+name = "defmt-macros"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
+checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
dependencies = [
+ "defmt-parser",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "defmt-parser"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
+dependencies = [
+ "thiserror 2.0.12",
]
[[package]]
name = "digest"
-version = "0.10.7"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer",
+ "const-oid",
"crypto-common",
- "subtle",
]
[[package]]
@@ -471,9 +487,15 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
[[package]]
name = "encode_unicode"
version = "1.0.0"
@@ -491,9 +513,9 @@ dependencies = [
[[package]]
name = "env_filter"
-version = "0.1.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
+checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
dependencies = [
"log",
"regex",
@@ -501,9 +523,9 @@ dependencies = [
[[package]]
name = "env_logger"
-version = "0.11.8"
+version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
+checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
dependencies = [
"anstream",
"anstyle",
@@ -547,15 +569,10 @@ dependencies = [
]
[[package]]
-name = "filetime_creation"
-version = "0.2.0"
+name = "find-msvc-tools"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c25b5d475550e559de5b0c0084761c65325444e3b6c9e298af9cefe7a9ef3a5f"
-dependencies = [
- "cfg-if",
- "filetime",
- "windows-sys 0.52.0",
-]
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "flate2"
@@ -574,21 +591,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
-[[package]]
-name = "foreign-types"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
-dependencies = [
- "foreign-types-shared",
-]
-
-[[package]]
-name = "foreign-types-shared"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
-
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@@ -599,19 +601,10 @@ dependencies = [
]
[[package]]
-name = "futures"
-version = "0.3.31"
+name = "fs_extra"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-executor",
- "futures-io",
- "futures-sink",
- "futures-task",
- "futures-util",
-]
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures-channel"
@@ -620,14 +613,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
- "futures-sink",
]
[[package]]
name = "futures-core"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
[[package]]
name = "futures-executor"
@@ -642,40 +634,39 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
[[package]]
name = "futures-macro"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
name = "futures-sink"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
[[package]]
name = "futures-task"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
[[package]]
name = "futures-util"
-version = "0.3.31"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
dependencies = [
- "futures-channel",
"futures-core",
"futures-io",
"futures-macro",
@@ -683,20 +674,9 @@ dependencies = [
"futures-task",
"memchr",
"pin-project-lite",
- "pin-utils",
"slab",
]
-[[package]]
-name = "generic-array"
-version = "0.14.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
-dependencies = [
- "typenum",
- "version_check",
-]
-
[[package]]
name = "getrandom"
version = "0.2.16"
@@ -704,8 +684,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
+ "wasm-bindgen",
]
[[package]]
@@ -716,15 +698,23 @@ checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
- "r-efi",
+ "r-efi 5.3.0",
"wasi 0.14.2+wasi-0.2.4",
]
[[package]]
-name = "gimli"
-version = "0.31.1"
+name = "getrandom"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi 6.0.0",
+ "rand_core",
+ "wasm-bindgen",
+]
[[package]]
name = "h2"
@@ -757,15 +747,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-[[package]]
-name = "hmac"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
-dependencies = [
- "digest",
-]
-
[[package]]
name = "http"
version = "1.3.1"
@@ -806,6 +787,15 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+[[package]]
+name = "hybrid-array"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
+dependencies = [
+ "typenum",
+]
+
[[package]]
name = "hyper"
version = "1.6.0"
@@ -842,22 +832,6 @@ dependencies = [
"tower-service",
]
-[[package]]
-name = "hyper-tls"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
-dependencies = [
- "bytes",
- "http-body-util",
- "hyper",
- "hyper-util",
- "native-tls",
- "tokio",
- "tokio-native-tls",
- "tower-service",
-]
-
[[package]]
name = "hyper-util"
version = "0.1.16"
@@ -877,35 +851,9 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"socket2",
- "system-configuration",
"tokio",
"tower-service",
"tracing",
- "windows-registry",
-]
-
-[[package]]
-name = "iana-time-zone"
-version = "0.1.63"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
-dependencies = [
- "android_system_properties",
- "core-foundation-sys",
- "iana-time-zone-haiku",
- "js-sys",
- "log",
- "wasm-bindgen",
- "windows-core",
-]
-
-[[package]]
-name = "iana-time-zone-haiku"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
-dependencies = [
- "cc",
]
[[package]]
@@ -1027,9 +975,9 @@ dependencies = [
[[package]]
name = "indicatif"
-version = "0.18.0"
+version = "0.18.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd"
+checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c"
dependencies = [
"console",
"portable-atomic",
@@ -1050,22 +998,12 @@ dependencies = [
[[package]]
name = "inout"
-version = "0.1.4"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "io-uring"
-version = "0.7.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
-dependencies = [
- "bitflags",
- "cfg-if",
- "libc",
+ "block-padding",
+ "hybrid-array",
]
[[package]]
@@ -1074,16 +1012,6 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
-[[package]]
-name = "iri-string"
-version = "0.7.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
-dependencies = [
- "memchr",
- "serde",
-]
-
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@@ -1098,26 +1026,82 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
-version = "0.2.15"
+version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
+checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
dependencies = [
+ "defmt",
+ "jiff-core",
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
- "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "jiff-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
+dependencies = [
+ "defmt",
]
[[package]]
name = "jiff-static"
-version = "0.2.15"
+version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
+checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
dependencies = [
+ "jiff-core",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys 0.3.1",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
+dependencies = [
+ "jni-sys 0.4.1",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
+
+[[package]]
+name = "jni-sys-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn 2.0.104",
]
[[package]]
@@ -1140,43 +1124,17 @@ dependencies = [
"wasm-bindgen",
]
-[[package]]
-name = "lazy_static"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
-
[[package]]
name = "libbz2-rs-sys"
-version = "0.2.2"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
+checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c"
[[package]]
name = "libc"
-version = "0.2.174"
+version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
-
-[[package]]
-name = "liblzma"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8"
-dependencies = [
- "liblzma-sys",
-]
-
-[[package]]
-name = "liblzma-sys"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
-]
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libredox"
@@ -1184,7 +1142,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
"libc",
"redox_syscall",
]
@@ -1200,9 +1158,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
-version = "0.9.4"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
@@ -1222,9 +1180,15 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.27"
+version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "lzma-rs"
@@ -1237,13 +1201,10 @@ dependencies = [
]
[[package]]
-name = "lzma-rust"
-version = "0.1.7"
+name = "lzma-rust2"
+version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baab2bbbd7d75a144d671e9ff79270e903957d92fb7386fd39034c709bd2661"
-dependencies = [
- "byteorder",
-]
+checksum = "12931b17820a3a0c48e46359d2be49ef57b5d50509fd5baba43e6c654977aa66"
[[package]]
name = "memchr"
@@ -1268,64 +1229,13 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.0.4"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
+checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
dependencies = [
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "native-tls"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
-dependencies = [
- "libc",
- "log",
- "openssl",
- "openssl-probe",
- "openssl-sys",
- "schannel",
- "security-framework",
- "security-framework-sys",
- "tempfile",
-]
-
-[[package]]
-name = "nt-time"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2de419e64947cd8830e66beb584acc3fb42ed411d103e3c794dda355d1b374b5"
-dependencies = [
- "chrono",
- "time",
-]
-
-[[package]]
-name = "num-conv"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
-
-[[package]]
-name = "num-traits"
-version = "0.2.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "object"
-version = "0.36.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
-dependencies = [
- "memchr",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1340,49 +1250,11 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
-[[package]]
-name = "openssl"
-version = "0.10.73"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
-dependencies = [
- "bitflags",
- "cfg-if",
- "foreign-types",
- "libc",
- "once_cell",
- "openssl-macros",
- "openssl-sys",
-]
-
-[[package]]
-name = "openssl-macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "openssl-probe"
-version = "0.1.6"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
-
-[[package]]
-name = "openssl-sys"
-version = "0.9.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
- "vcpkg",
-]
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "option-ext"
@@ -1413,16 +1285,6 @@ dependencies = [
"windows-targets 0.52.6",
]
-[[package]]
-name = "pbkdf2"
-version = "0.12.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
-dependencies = [
- "digest",
- "hmac",
-]
-
[[package]]
name = "percent-encoding"
version = "2.3.1"
@@ -1435,12 +1297,6 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
-
[[package]]
name = "pkg-config"
version = "0.3.32"
@@ -1449,9 +1305,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
-version = "1.11.1"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
+checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
[[package]]
name = "portable-atomic-util"
@@ -1471,32 +1327,83 @@ dependencies = [
"zerovec",
]
-[[package]]
-name = "powerfmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
-
[[package]]
name = "ppmd-rust"
-version = "1.2.1"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b"
+checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
[[package]]
name = "proc-macro2"
-version = "1.0.95"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
-name = "quote"
-version = "1.0.40"
+name = "quinn"
+version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror 2.0.12",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
+dependencies = [
+ "aws-lc-rs",
+ "bytes",
+ "getrandom 0.4.3",
+ "lru-slab",
+ "rand",
+ "rand_pcg",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.12",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
@@ -1507,13 +1414,45 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "rand_pcg"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
+dependencies = [
+ "rand_core",
+]
+
[[package]]
name = "redox_syscall"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
]
[[package]]
@@ -1524,14 +1463,14 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"libredox",
- "thiserror",
+ "thiserror 2.0.12",
]
[[package]]
name = "regex"
-version = "1.11.1"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
@@ -1541,9 +1480,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.9"
+version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [
"aho-corasick",
"memchr",
@@ -1552,20 +1491,19 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.8.5"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "reqwest"
-version = "0.12.22"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
+checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
dependencies = [
"base64",
"bytes",
"encoding_rs",
- "futures-channel",
"futures-core",
"futures-util",
"h2",
@@ -1574,21 +1512,21 @@ dependencies = [
"http-body-util",
"hyper",
"hyper-rustls",
- "hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
- "native-tls",
"percent-encoding",
"pin-project-lite",
+ "quinn",
+ "rustls",
"rustls-pki-types",
+ "rustls-platform-verifier",
"serde",
"serde_json",
- "serde_urlencoded",
"sync_wrapper",
"tokio",
- "tokio-native-tls",
+ "tokio-rustls",
"tokio-util",
"tower",
"tower-http",
@@ -1598,6 +1536,7 @@ dependencies = [
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
+ "webpki-roots",
]
[[package]]
@@ -1615,18 +1554,18 @@ dependencies = [
]
[[package]]
-name = "rustc-demangle"
-version = "0.1.26"
+name = "rustc-hash"
+version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rustix"
-version = "1.0.8"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
"errno",
"libc",
"linux-raw-sys",
@@ -1639,6 +1578,7 @@ version = "0.23.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
dependencies = [
+ "aws-lc-rs",
"once_cell",
"rustls-pki-types",
"rustls-webpki",
@@ -1646,21 +1586,62 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "rustls-native-certs"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
+dependencies = [
+ "openssl-probe",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
+ "web-time",
"zeroize",
]
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
+dependencies = [
+ "core-foundation",
+ "core-foundation-sys",
+ "jni",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-native-certs",
+ "rustls-platform-verifier-android",
+ "rustls-webpki",
+ "security-framework",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
[[package]]
name = "rustls-webpki"
version = "0.103.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
dependencies = [
+ "aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -1672,12 +1653,6 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-[[package]]
-name = "ryu"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
-
[[package]]
name = "same-file"
version = "1.0.6"
@@ -1687,22 +1662,13 @@ dependencies = [
"winapi-util",
]
-[[package]]
-name = "scc"
-version = "2.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4"
-dependencies = [
- "sdd",
-]
-
[[package]]
name = "schannel"
-version = "0.1.27"
+version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1711,19 +1677,13 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-[[package]]
-name = "sdd"
-version = "3.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
-
[[package]]
name = "security-framework"
-version = "2.11.1"
+version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -1732,9 +1692,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.14.0"
+version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
@@ -1742,106 +1702,95 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.219"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.219"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.3",
]
[[package]]
name = "serde_json"
-version = "1.0.142"
+version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "serde_urlencoded"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
-dependencies = [
- "form_urlencoded",
- "itoa",
- "ryu",
"serde",
+ "serde_core",
+ "zmij",
]
[[package]]
name = "serial_test"
-version = "3.2.0"
+version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9"
+checksum = "a6df5ed973ad8d834e09f824f9e9f449af6b9a3745f78dec7cc752770bd3bf11"
dependencies = [
- "futures",
+ "futures-executor",
+ "futures-util",
"log",
"once_cell",
"parking_lot",
- "scc",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
-version = "3.2.0"
+version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
+checksum = "a22144e767da4ddd8416dbf383700542ffd8a5dc493dfecedfe1fe3ad03c98ae"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.3",
]
[[package]]
-name = "sevenz-rust"
-version = "0.6.1"
+name = "sevenz-rust2"
+version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26482cf1ecce4540dc782fc70019eba89ffc4d87b3717eb5ec524b5db6fdefef"
+checksum = "20acd38118edb830eb5a0bfacaf83c687b9995f48d6983cdac3cb18ce1d571cc"
dependencies = [
- "bit-set",
- "byteorder",
- "crc",
- "filetime_creation",
+ "aes",
+ "bzip2",
+ "cbc",
+ "crc32fast",
+ "getrandom 0.4.3",
"js-sys",
- "lzma-rust",
- "nt-time",
+ "lzma-rust2",
+ "ppmd-rust",
"sha2",
"wasm-bindgen",
]
-[[package]]
-name = "sha1"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
[[package]]
name = "sha2"
-version = "0.10.9"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -1850,9 +1799,9 @@ dependencies = [
[[package]]
name = "shlex"
-version = "1.3.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "signal-hook-registry"
@@ -1883,12 +1832,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
-version = "0.6.0"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
dependencies = [
"libc",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -1920,6 +1869,17 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
[[package]]
name = "sync_wrapper"
version = "1.0.2"
@@ -1937,35 +1897,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn",
-]
-
-[[package]]
-name = "system-configuration"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
-dependencies = [
- "bitflags",
- "core-foundation",
- "system-configuration-sys",
-]
-
-[[package]]
-name = "system-configuration-sys"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
-dependencies = [
- "core-foundation-sys",
- "libc",
+ "syn 2.0.104",
]
[[package]]
name = "tar"
-version = "0.4.44"
+version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
+checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
@@ -1974,15 +1913,24 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.20.0"
+version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
- "getrandom 0.3.3",
+ "getrandom 0.4.3",
"once_cell",
"rustix",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
]
[[package]]
@@ -1991,7 +1939,18 @@ version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
- "thiserror-impl",
+ "thiserror-impl 2.0.12",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
]
[[package]]
@@ -2002,37 +1961,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
- "syn",
-]
-
-[[package]]
-name = "time"
-version = "0.3.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
-dependencies = [
- "deranged",
- "num-conv",
- "powerfmt",
- "serde",
- "time-core",
- "time-macros",
-]
-
-[[package]]
-name = "time-core"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
-
-[[package]]
-name = "time-macros"
-version = "0.2.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
-dependencies = [
- "num-conv",
- "time-core",
+ "syn 2.0.104",
]
[[package]]
@@ -2046,44 +1975,46 @@ dependencies = [
]
[[package]]
-name = "tokio"
-version = "1.47.1"
+name = "tinyvec"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
dependencies = [
- "backtrace",
"bytes",
- "io-uring",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
- "slab",
"socket2",
"tokio-macros",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
-version = "2.5.0"
+version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [
"proc-macro2",
"quote",
- "syn",
-]
-
-[[package]]
-name = "tokio-native-tls"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
-dependencies = [
- "native-tls",
- "tokio",
+ "syn 3.0.3",
]
[[package]]
@@ -2126,20 +2057,20 @@ dependencies = [
[[package]]
name = "tower-http"
-version = "0.6.6"
+version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
"bytes",
"futures-util",
"http",
"http-body",
- "iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
+ "url",
]
[[package]]
@@ -2180,10 +2111,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
-name = "typenum"
-version = "1.18.0"
+name = "typed-path"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-ident"
@@ -2234,27 +2171,15 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.17.0"
+version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
+checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
dependencies = [
- "getrandom 0.3.3",
+ "getrandom 0.4.3",
"js-sys",
"wasm-bindgen",
]
-[[package]]
-name = "vcpkg"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
-
-[[package]]
-name = "version_check"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-
[[package]]
name = "walkdir"
version = "2.5.0"
@@ -2311,7 +2236,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
"wasm-bindgen-shared",
]
@@ -2346,7 +2271,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -2393,6 +2318,24 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "winapi-util"
version = "0.1.9"
@@ -2402,41 +2345,6 @@ dependencies = [
"windows-sys 0.59.0",
]
-[[package]]
-name = "windows-core"
-version = "0.61.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
-dependencies = [
- "windows-implement",
- "windows-interface",
- "windows-link",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-implement"
-version = "0.60.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "windows-interface"
-version = "0.59.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "windows-link"
version = "0.1.3"
@@ -2444,32 +2352,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
-name = "windows-registry"
-version = "0.5.3"
+name = "windows-link"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
-dependencies = [
- "windows-link",
- "windows-result",
- "windows-strings",
-]
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
-name = "windows-result"
-version = "0.3.4"
+name = "windows-sys"
+version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-strings"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
-dependencies = [
- "windows-link",
+ "windows-targets 0.42.2",
]
[[package]]
@@ -2499,6 +2393,30 @@ dependencies = [
"windows-targets 0.53.3",
]
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
[[package]]
name = "windows-targets"
version = "0.52.6"
@@ -2521,7 +2439,7 @@ version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
- "windows-link",
+ "windows-link 0.1.3",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
@@ -2532,6 +2450,12 @@ dependencies = [
"windows_x86_64_msvc 0.53.0",
]
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
@@ -2544,6 +2468,12 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
@@ -2556,6 +2486,12 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@@ -2580,6 +2516,12 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
@@ -2592,6 +2534,12 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
@@ -2604,6 +2552,12 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
@@ -2616,6 +2570,12 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
@@ -2634,7 +2594,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
]
[[package]]
@@ -2673,7 +2633,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
"synstructure",
]
@@ -2694,7 +2654,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
"synstructure",
]
@@ -2703,20 +2663,6 @@ name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
-dependencies = [
- "zeroize_derive",
-]
-
-[[package]]
-name = "zeroize_derive"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
[[package]]
name = "zerotrie"
@@ -2748,32 +2694,20 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.104",
]
[[package]]
name = "zip"
-version = "4.3.0"
+version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b"
+checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
dependencies = [
- "aes",
- "arbitrary",
- "bzip2",
- "constant_time_eq",
"crc32fast",
- "deflate64",
"flate2",
- "getrandom 0.3.3",
- "hmac",
"indexmap",
- "liblzma",
"memchr",
- "pbkdf2",
- "ppmd-rust",
- "sha1",
- "time",
- "zeroize",
+ "typed-path",
"zopfli",
"zstd",
]
@@ -2785,10 +2719,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
[[package]]
-name = "zopfli"
-version = "0.8.2"
+name = "zmij"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+
+[[package]]
+name = "zopfli"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
dependencies = [
"bumpalo",
"crc32fast",
diff --git a/Cargo.toml b/Cargo.toml
index de7a5db..1374cec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,37 +9,39 @@ version = "0.2.2"
edition = "2021"
[dependencies]
-clap = { version = "4.4", features = ["derive"] }
-tokio = { version = "1.0", features = ["full"] }
-reqwest = { version = "0.12", features = ["stream", "json"] }
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-lazy_static = "1.4"
-uuid = { version = "1.0", features = ["v4"] }
-sha2 = "0.10"
-zip = "4"
+clap = { version = "4.6", features = ["derive"] }
+tokio = { version = "1.53", features = ["full"] }
+# rustls rather than the default native-tls: it drops the OpenSSL system dependency, so
+# `cargo install banderole` no longer needs libssl-dev / pkg-config on Linux.
+reqwest = { version = "0.13", default-features = false, features = [
+ "stream",
+ "json",
+ "rustls",
+ "webpki-roots",
+ "charset",
+ "http2",
+] }
+serde = { version = "1.0.229", features = ["derive"] }
+serde_json = "1.0.151"
+uuid = { version = "1.24", features = ["v4"] }
+zip = { version = "8", default-features = false, features = ["deflate", "zstd"] }
directories = "6"
-anyhow = "1.0"
-walkdir = "2.4"
-futures-util = "0.3"
-chrono = { version = "0.4", features = ["serde"] }
-tempfile = "3.20"
-base64 = "0.22"
-indicatif = "0.18"
-log = "0.4"
-env_logger = "0.11"
-indicatif-log-bridge = "0.2"
-console = "0.16"
-flate2 = "1.0"
-tar = "0.4"
-sevenz-rust = "0.6"
+anyhow = "1.0.104"
+walkdir = "2.5"
+futures-util = "0.3.33"
+tempfile = "3.27"
+indicatif = "0.18.6"
+log = "0.4.33"
+env_logger = "0.11.11"
+indicatif-log-bridge = "0.2.3"
+console = "0.16.4"
+tar = "0.4.46"
+# sevenz-rust is unmaintained; sevenz-rust2 is the maintained fork.
+sevenz-rust2 = "0.21"
lzma-rs = "0.3"
-[build-dependencies]
-reqwest = { version = "0.12", features = ["blocking"] }
-
[dev-dependencies]
-serial_test = "3"
+serial_test = "4"
[[test]]
name = "integration_test"
@@ -61,6 +63,10 @@ harness = true
name = "concurrent_execution_integration_test"
harness = true
+[[test]]
+name = "launcher_semantics_integration_test"
+harness = true
+
# Run tests sequentially to avoid resource conflicts
[profile.test]
opt-level = 0
diff --git a/README.md b/README.md
index 9068789..fb7320c 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,41 @@ Create cross-platform single-executables for Node.js projects. Windows is not su
Banderole bundles your Node.js app, all dependencies, and a portable Node binary into a single native executable. On first launch, it unpacks to a cache directory for fast subsequent executions.
-Unlike [Node.js SEA](https://nodejs.org/api/single-executable-applications.html) or [pkg](https://github.com/yao-pkg/pkg), banderole handles complex projects with dynamic imports and non-JavaScript files without requiring patches, but since it includes all dependencies by default, it has significantly larger filesize.
+Unlike [Node.js SEA](https://nodejs.org/api/single-executable-applications.html) or [pkg](https://github.com/yao-pkg/pkg), banderole handles complex projects with dynamic imports and non-JavaScript files without requiring patches — it ships a stock Node binary and your real dependency tree rather than a patched runtime and a virtual filesystem.
+
+## Performance
+
+Measured on linux-x64, Node 22.17.1, against [`@yao-pkg/pkg`](https://github.com/yao-pkg/pkg) 6.22.0 building the same app. Startup is the median of 60 interleaved runs (targets rotate every round, so machine drift affects both equally).
+
+| | trivial app | | app with deps
(express, lodash, chalk, dayjs) | |
+|---|---|---|---|---|
+| | **banderole** | pkg | **banderole** | pkg |
+| Executable size | **33,416,408 B** | 74,306,703 B | **35,366,424 B** | 77,346,956 B |
+| Startup (median) | **16.1 ms** | 25.7 ms | **53.1 ms** | 78.8 ms |
+| Resident processes | **1** | 1 | **1** | 1 |
+| Resident memory (RSS) | **43.8 MiB** | 49.4 MiB | **66.4 MiB** | 76.2 MiB |
+
+pkg's size floor is the uncompressed Node binary it embeds, which its `--compress` option does not touch. banderole compresses the Node binary with zstd and ships only the executable itself, so a bundle is roughly half the size.
+
+The trade-off is the **first** launch, which unpacks the payload into the cache directory: 117 ms versus pkg's 30 ms for the trivial app. Every launch after that takes the warm path above.
+
+### Scaling with project size
+
+First-launch extraction grows with the dependency tree; steady-state launch does not, because it is one file read followed by `exec`.
+
+| `node_modules` | files | bundle time | first launch | **every later launch** |
+|---|---|---|---|---|
+| — | 0 | 16 s | 159 ms | **19.4 ms** |
+| 14 MB | 5,500 | 18 s | 224 ms | **22.4 ms** |
+| 68 MB (real npm tree) | 9,725 | 19 s | 316 ms | **71.2 ms** |
+| 143 MB | 27,500 | 20 s | 348 ms | **23.3 ms** |
+| 763 MB | 110,000 | 25 s | 862 ms | **22.9 ms** |
+
+A 763 MB dependency tree still unpacks in under a second, and warm launch is flat across the whole range. (The 68 MB row is a real npm install; its higher warm figure is Node resolving express/lodash/dayjs at runtime, not launcher overhead. The other rows are generated trees, which compress better than real ones.)
## Requirements
-Banderole requires the Rust toolchain to be installed on your system to build portable executables.
+Banderole requires a Rust toolchain (`cargo`, `rustc`, `rustup`) **and a working C linker** — `cc`/`gcc`/`clang` on Unix, MSVC on Windows — because it compiles a small launcher for each bundle.
## Installation
diff --git a/deny.toml b/deny.toml
index b3bfff6..f38f078 100644
--- a/deny.toml
+++ b/deny.toml
@@ -10,7 +10,11 @@ allow = [
"CC0-1.0",
"MPL-2.0",
"Zlib",
- "bzip2-1.0.6"
+ "bzip2-1.0.6",
+ # rustls stack (ring, untrusted, rustls-webpki) — replaces the OpenSSL/native-tls path.
+ "ISC",
+ # Mozilla root certificate bundle shipped by webpki-roots.
+ "CDLA-Permissive-2.0",
]
confidence-threshold = 0.8
diff --git a/src/bundler.rs b/src/bundler.rs
index 1776b8c..d33442b 100644
--- a/src/bundler.rs
+++ b/src/bundler.rs
@@ -14,6 +14,27 @@ use std::time::Instant;
use zip::ZipWriter;
+/// Zstd level for application and dependency files.
+///
+/// This is chosen for the *per-entry* cost, not for throughput. `ZipWriter` builds a fresh
+/// streaming encoder for every entry and cannot pledge the source size, so zstd commits to
+/// the level's full parameter set and allocates its match tables per file regardless of how
+/// small that file is. Measured cost of one encoder over a 108-byte input:
+///
+/// ```text
+/// level 3 → 148 us level 9 → 850 us level 12 → 30,021 us
+/// ```
+///
+/// That 35x cliff between 9 and 12 is fixed overhead, so it multiplies by file count: a
+/// 100,000-file dependency tree costs ~15 s at level 3 and ~50 minutes at level 12, for
+/// roughly 9% difference in size. Nothing above 9 is defensible for many-file payloads.
+const APP_COMPRESSION_LEVEL: i64 = 3;
+
+/// Zstd level for the single Node executable. One entry pays the per-entry overhead once,
+/// and this is a fixed ~105 MB that dominates the output size, so compressing it hard is
+/// worth a few seconds.
+const NODE_COMPRESSION_LEVEL: i64 = 12;
+
/// Public entry-point used by `main.rs`.
///
/// * `project_path` – path that contains a `package.json`.
@@ -27,7 +48,7 @@ pub async fn bundle_project(
project_path: PathBuf,
output_path: Option,
custom_name: Option,
- _no_compression: bool,
+ no_compression: bool,
ignore_cached_versions: bool,
multi: &MultiProgress,
) -> Result<()> {
@@ -133,10 +154,29 @@ pub async fn bundle_project(
let mut zip_data: Vec = Vec::new();
{
let mut zip = ZipWriter::new(std::io::Cursor::new(&mut zip_data));
- // Always use uncompressed (Stored) for near-instant extraction
- // This makes the executable larger but launch time is much faster
- let opts: zip::write::FileOptions<'static, ()> =
- zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
+ // Zstd is strictly better than Stored here: it cuts the payload several-fold and its
+ // decompression is fast enough that the one-time first-run extraction still gets
+ // *faster*, because there are far fewer bytes to write to disk. Warm launches never
+ // touch the archive at all, so they are unaffected either way.
+ //
+ // The level differs by payload because the two halves scale differently. Measured on
+ // real node_modules content, zstd throughput is 95.9 MB/s at level 3 but only
+ // 17.6 MB/s at level 12 — and an application's dependency tree is unbounded, so a
+ // high level there turns bundling a large project into minutes of compression. The
+ // Node binary is a fixed ~105 MB that dominates the output size, so it is worth
+ // compressing hard; a couple of seconds buys ~25% off the dominant term.
+ let opts: zip::write::FileOptions<'static, ()> = if no_compression {
+ zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored)
+ } else {
+ zip::write::FileOptions::default()
+ .compression_method(zip::CompressionMethod::Zstd)
+ .compression_level(Some(APP_COMPRESSION_LEVEL))
+ };
+ let node_opts: zip::write::FileOptions<'static, ()> = if no_compression {
+ opts
+ } else {
+ opts.compression_level(Some(NODE_COMPRESSION_LEVEL))
+ };
// Pre-count app files
let app_files = count_files_in_dir(&source_dir, true, true);
@@ -159,17 +199,11 @@ pub async fn bundle_project(
Some(&pb_bundle),
)?;
- // Count node runtime files and extend length
- let node_files = count_files_in_dir(node_root, false, true);
- let new_len = pb_bundle.length().unwrap_or(0) + node_files;
+ // Only the Node executable itself is ever resolved at runtime, so the rest of the
+ // distribution (headers, npm, corepack, docs, man pages) is dead weight.
+ let new_len = pb_bundle.length().unwrap_or(0) + 1;
pb_bundle.set_length(new_len);
- add_dir_to_zip(
- &mut zip,
- node_root,
- Path::new("node"),
- opts,
- Some(&pb_bundle),
- )?;
+ add_node_runtime_to_zip(&mut zip, node_root, node_opts, Some(&pb_bundle))?;
zip.finish()?;
}
pb_bundle.finish_and_clear();
@@ -230,6 +264,92 @@ fn count_files_in_dir(dir: &Path, exclude_node_modules: bool, follow_links: bool
count
}
+/// Add just the Node.js executable to the archive, at the path the launcher resolves.
+///
+/// The launcher only ever execs `node/bin/node` (or `node/node.exe`), so bundling the whole
+/// distribution ships ~65 MB of headers, npm and docs that nothing can reach. The binary is
+/// also stripped first where the platform allows it, which removes another ~14%.
+fn add_node_runtime_to_zip(
+ zip: &mut ZipWriter,
+ node_root: &Path,
+ opts: zip::write::FileOptions<'static, ()>,
+ progress: Option<&ProgressBar>,
+) -> Result<()>
+where
+ W: Write + Read + std::io::Seek,
+{
+ let (source_rel, archive_path) = if cfg!(windows) {
+ (PathBuf::from("node.exe"), "node/node.exe")
+ } else {
+ (PathBuf::from("bin").join("node"), "node/bin/node")
+ };
+
+ let node_binary = node_root.join(&source_rel);
+ anyhow::ensure!(
+ node_binary.exists(),
+ "Node.js executable not found at {}",
+ node_binary.display()
+ );
+
+ let node_binary = strip_node_binary(&node_binary).unwrap_or(node_binary);
+
+ let opts = opts.unix_permissions(0o755).large_file(true);
+ zip.start_file(archive_path, opts)?;
+
+ let mut file = fs::File::open(&node_binary)
+ .with_context(|| format!("Failed to open {}", node_binary.display()))?;
+ std::io::copy(&mut file, zip)
+ .with_context(|| format!("Failed to add {} to archive", node_binary.display()))?;
+
+ if let Some(pb) = progress {
+ pb.inc(1);
+ }
+
+ Ok(())
+}
+
+/// Produce a stripped copy of the Node binary next to the cached distribution, reusing it on
+/// subsequent bundles. Returns `None` when stripping is unavailable or fails, in which case
+/// the caller falls back to the original binary.
+fn strip_node_binary(node_binary: &Path) -> Option {
+ // macOS ships signed binaries; stripping invalidates the signature and Gatekeeper then
+ // kills the process, so leave those alone.
+ if cfg!(target_os = "macos") || cfg!(windows) {
+ return None;
+ }
+
+ let stripped = node_binary.with_extension("stripped");
+ if let (Ok(orig), Ok(strip_meta)) = (node_binary.metadata(), stripped.metadata()) {
+ // Reuse only if the stripped copy is newer than the original and non-empty.
+ if strip_meta.len() > 0 {
+ if let (Ok(a), Ok(b)) = (orig.modified(), strip_meta.modified()) {
+ if b >= a {
+ return Some(stripped);
+ }
+ }
+ }
+ }
+
+ fs::copy(node_binary, &stripped).ok()?;
+ let status = std::process::Command::new("strip")
+ .arg("--strip-all")
+ .arg(&stripped)
+ .stdout(std::process::Stdio::null())
+ .stderr(std::process::Stdio::null())
+ .status();
+
+ match status {
+ Ok(s) if s.success() => {
+ debug!("Stripped Node binary at {}", stripped.display());
+ Some(stripped)
+ }
+ _ => {
+ let _ = fs::remove_file(&stripped);
+ None
+ }
+ }
+}
+
/// Bundle dependencies with improved package manager support
fn bundle_dependencies(
zip: &mut ZipWriter,
@@ -1360,56 +1480,6 @@ fn resolve_output_path(
// Utility helpers
// ────────────────────────────────────────────────────────────────────────────
-fn add_dir_to_zip(
- zip: &mut ZipWriter,
- src_dir: &Path,
- dest_dir: &Path,
- opts: zip::write::FileOptions<'static, ()>,
- progress: Option<&ProgressBar>,
-) -> Result<()>
-where
- W: Write + Read + std::io::Seek,
-{
- for entry in walkdir::WalkDir::new(src_dir).follow_links(true) {
- let entry = entry?;
- let path = entry.path();
- let rel_path = path.strip_prefix(src_dir).unwrap();
- let zip_path = dest_dir.join(rel_path);
-
- if entry.file_type().is_dir() {
- zip.add_directory(zip_path.to_string_lossy().as_ref(), opts)?;
- continue;
- }
-
- if !entry.file_type().is_file() && !entry.file_type().is_symlink() {
- continue;
- }
-
- let file_opts = {
- #[cfg(unix)]
- {
- use std::os::unix::fs::PermissionsExt;
- let metadata = fs::metadata(path)?;
- let permissions = metadata.permissions();
- let mode = permissions.mode();
- opts.unix_permissions(mode)
- }
- #[cfg(not(unix))]
- {
- opts
- }
- };
-
- zip.start_file(zip_path.to_string_lossy().as_ref(), file_opts)?;
- let data = fs::read(path).context("Failed to read file while zipping")?;
- zip.write_all(&data)?;
- if let Some(pb) = progress {
- pb.inc(1);
- }
- }
- Ok(())
-}
-
/// Add directory to zip without following symlinks but preserving them
fn add_dir_to_zip_no_follow(
zip: &mut ZipWriter,
diff --git a/src/executable.rs b/src/executable.rs
index aef66b9..9e0672b 100644
--- a/src/executable.rs
+++ b/src/executable.rs
@@ -105,6 +105,10 @@ fn build_executable_with_progress(
// Actual build; consume Cargo JSON messages to compute progress without a dry-run
let mut cmd = Command::new("cargo");
cmd.current_dir(build_dir)
+ // A CARGO_TARGET_DIR inherited from the caller's environment would redirect the
+ // launcher build somewhere else entirely, and we would then fail to find the binary
+ // under build_dir/target.
+ .env_remove("CARGO_TARGET_DIR")
.args([
"build",
"--release",
@@ -323,25 +327,45 @@ fn build_executable_with_progress(
.ok()
.map(|s| s.clone())
.unwrap_or_default();
- let trim_tail = |mut s: String| {
+ // Keep the *end* of a long log: that is where the failure is. (The previous version
+ // discarded the result of `split_off`, which truncated the string in place and then
+ // printed the middle of the log, never the error itself.)
+ let trim_tail = |s: String| {
const MAX: usize = 4000;
- if s.len() > MAX {
- s.split_off(s.len() - MAX)
- } else {
- String::new()
- };
- if s.len() > MAX {
- s[s.len() - MAX..].to_string()
- } else {
- s
+ match s.char_indices().nth_back(MAX.saturating_sub(1)) {
+ Some((idx, _)) if s.len() > MAX => s[idx..].to_string(),
+ _ => s,
}
};
- let out_tail = trim_tail(out);
- let err_tail = trim_tail(err);
+
+ // cargo emits diagnostics as JSON on stdout; surface the rendered compiler errors
+ // directly instead of making the user read the raw message stream.
+ let diagnostics: Vec = out
+ .lines()
+ .filter_map(|line| serde_json::from_str::(line).ok())
+ .filter(|v| v.get("reason").and_then(|r| r.as_str()) == Some("compiler-message"))
+ .filter_map(|v| {
+ let msg = v.get("message")?;
+ if msg.get("level").and_then(|l| l.as_str()) != Some("error") {
+ return None;
+ }
+ msg.get("rendered")
+ .and_then(|r| r.as_str())
+ .map(|s| s.to_string())
+ })
+ .collect();
+
+ if !diagnostics.is_empty() {
+ anyhow::bail!(
+ "Cargo build failed.\nCompiler errors:\n{}",
+ trim_tail(diagnostics.join("\n"))
+ );
+ }
+
anyhow::bail!(
"Cargo build failed.\nLast stdout:\n{}\nLast stderr:\n{}",
- out_tail,
- err_tail
+ trim_tail(out),
+ trim_tail(err)
);
}
@@ -393,6 +417,7 @@ fn compute_total_via_cargo_metadata(build_dir: &Path, target_triple: &str) -> Re
fn run_metadata(build_dir: &Path, args: &[&str]) -> Result {
let output = Command::new("cargo")
.current_dir(build_dir)
+ .env_remove("CARGO_TARGET_DIR")
.args(args)
.output()
.with_context(|| format!("Failed to run cargo {}", args.join(" ")))?;
diff --git a/src/node_downloader.rs b/src/node_downloader.rs
index a4b0109..9f072f4 100644
--- a/src/node_downloader.rs
+++ b/src/node_downloader.rs
@@ -3,17 +3,15 @@ use crate::platform::Platform;
use anyhow::{Context, Result};
use futures_util::StreamExt;
use indicatif::{ProgressBar, ProgressStyle};
-use lazy_static::lazy_static;
use log::info;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
-use std::sync::Mutex;
+use std::sync::{LazyLock, Mutex};
use tokio::fs;
use tokio::io::AsyncWriteExt;
-lazy_static! {
- static ref NODE_VERSION_CACHE: Mutex> = Mutex::new(HashMap::new());
-}
+static NODE_VERSION_CACHE: LazyLock>> =
+ LazyLock::new(|| Mutex::new(HashMap::new()));
pub struct NodeDownloader {
platform: Platform,
@@ -295,7 +293,7 @@ impl NodeDownloader {
if let Some(pb) = &progress {
pb.set_message("Extracting 7z archive");
}
- sevenz_rust::decompress_file(&archive_path, &target_dir)
+ sevenz_rust2::decompress_file(&archive_path, &target_dir)
.context("Failed to extract 7z archive")?;
// Post-process: many Node archives have a single top-level folder. Flatten it.
diff --git a/src/node_version_manager.rs b/src/node_version_manager.rs
index 1caa953..0088550 100644
--- a/src/node_version_manager.rs
+++ b/src/node_version_manager.rs
@@ -1,13 +1,11 @@
use anyhow::{Context, Result};
-use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
-use std::sync::Mutex;
+use std::sync::{LazyLock, Mutex};
use tokio::time::{Duration, Instant};
-lazy_static! {
- static ref VERSION_CACHE: Mutex = Mutex::new(VersionCache::new());
-}
+static VERSION_CACHE: LazyLock> =
+ LazyLock::new(|| Mutex::new(VersionCache::new()));
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeVersion {
diff --git a/src/template/crg.toml b/src/template/crg.toml
index 775864d..82db19f 100644
--- a/src/template/crg.toml
+++ b/src/template/crg.toml
@@ -6,19 +6,21 @@ edition = "2021"
[dependencies]
anyhow = "1.0"
directories = "6"
-zip = "4"
+# Only the codecs the bundler can actually emit. The default feature set drags in bzip2,
+# liblzma, aes, zopfli, deflate64 and ppmd-rust, none of which are ever used.
+zip = { version = "8", default-features = false, features = ["deflate", "zstd"] }
serde_json = "1.0"
-fs2 = "0.4"
-walkdir = "2.4"
-rayon = "1.10"
+# fs2 is unmaintained (last release 2018); fs4 is the maintained fork.
+fs4 = { version = "1.1", default-features = false, features = ["sync"] }
[build-dependencies]
# No build dependencies needed - data is embedded at compile time
-# Optimize for size and performance
+# The launcher's own code is a rounding error next to the embedded payload, so optimize the
+# extraction path for speed rather than for size.
[profile.release]
-opt-level = "z" # Optimize for size
-lto = true # Enable Link Time Optimization
-codegen-units = 1 # Reduce number of codegen units to increase optimizations
-panic = "abort" # Abort on panic (smaller binary)
-strip = true # Strip symbols from binary
+opt-level = 3
+lto = "thin"
+codegen-units = 1
+panic = "abort"
+strip = true
diff --git a/src/template/src/main.rs b/src/template/src/main.rs
index b34adf5..9530418 100644
--- a/src/template/src/main.rs
+++ b/src/template/src/main.rs
@@ -1,449 +1,394 @@
use anyhow::{Context, Result};
use directories::BaseDirs;
-use fs2::FileExt;
-use rayon::prelude::*;
+use fs4::FileExt;
use std::env;
use std::fs;
-use std::io::Cursor;
+use std::collections::BTreeSet;
+use std::io::{Cursor, Read, Write};
use std::path::{Path, PathBuf};
-use std::process::{Command, Stdio};
+use std::process::Command;
+use std::sync::atomic::{AtomicUsize, Ordering};
use zip::ZipArchive;
// These will be replaced during the build process with actual embedded data
// The build script will generate a data.rs file with the actual data
include!(concat!(env!("OUT_DIR"), "/data.rs"));
+/// Name of the marker file written after a successful extraction. Its contents are the
+/// resolved main script path, so the warm path never has to parse package.json.
+const READY_FILE: &str = ".ready";
+
+/// Entries at or below this size are decompressed into a reusable buffer and written with a
+/// single `write_all`; larger ones stream through a buffered writer.
+const SMALL_ENTRY_BYTES: u64 = 8 * 1024 * 1024;
+
+/// One archive entry's destination, resolved during the planning pass so the parallel write
+/// pass never has to touch shared metadata.
+struct PlannedFile {
+ index: usize,
+ path: PathBuf,
+ size: u64,
+ #[cfg(unix)]
+ mode: Option,
+}
+
fn main() -> Result<()> {
- let args: Vec = env::args().collect();
+ // args_os, not args: a bundled app must be able to receive non-UTF-8 arguments such as
+ // a filename in an arbitrary encoding. `env::args()` panics on those.
+ let args: Vec = env::args_os().collect();
- // Get cache directory
- let cache_dir = get_cache_dir().context("Failed to determine cache directory")?;
- let app_dir = cache_dir.join(&BUILD_ID);
- let ready_file = app_dir.join(".ready");
+ let cache_dir = get_cache_dir_fast().context("Failed to determine cache directory")?;
+ let app_dir = cache_dir.join(BUILD_ID);
- // Check if already extracted and ready
- if ready_file.exists() && is_extraction_valid(&app_dir)? {
- return run_app(&app_dir, &args[1..]);
+ // Warm path: a single read of the ready marker gives us everything we need. If anything
+ // about the cache is stale or damaged, `run_app` returns and we fall through to a full
+ // re-extraction below, so we do not need to stat the payload up front.
+ if let Ok(main_script) = fs::read_to_string(app_dir.join(READY_FILE)) {
+ let main_script = main_script.trim();
+ if !main_script.is_empty() {
+ run_app(&app_dir, main_script, &args[1..])?;
+ }
}
- // Use file locking to prevent concurrent extraction
- let lock_file_path = cache_dir.join(format!("{}.lock", BUILD_ID));
+ // Cold path: extract under an exclusive lock so concurrent launches cooperate.
+ fs::create_dir_all(&cache_dir).context("Failed to create cache directory")?;
+ let lock_file_path = cache_dir.join(format!("{BUILD_ID}.lock"));
let lock_file = fs::OpenOptions::new()
.create(true)
.write(true)
+ .truncate(false)
.open(&lock_file_path)
.with_context(|| format!("Failed to create lock file at {}", lock_file_path.display()))?;
- // Acquire exclusive lock
- lock_file
- .lock_exclusive()
- .context("Failed to acquire extraction lock")?;
+ FileExt::lock(&lock_file).context("Failed to acquire extraction lock")?;
- // Double-check if extraction completed while waiting for lock
- if ready_file.exists() && is_extraction_valid(&app_dir)? {
- // Release lock and run
- lock_file.unlock().ok();
- return run_app(&app_dir, &args[1..]);
+ // Another process may have completed the extraction while we waited for the lock. Keep
+ // holding the lock across this attempt: a successful exec drops it automatically (the fd
+ // is close-on-exec), and if the cache turns out to be unusable we still own the right to
+ // re-extract below.
+ let ready_path = app_dir.join(READY_FILE);
+ if let Ok(main_script) = fs::read_to_string(&ready_path) {
+ let main_script = main_script.trim().to_string();
+ if !main_script.is_empty() {
+ run_app(&app_dir, &main_script, &args[1..])?;
+ }
}
- // Extract application if needed
extract_application(&app_dir)
.with_context(|| format!("Failed to extract application to {}", app_dir.display()))?;
- // Mark as ready
- fs::write(&ready_file, "ready")
- .with_context(|| format!("Failed to create ready file at {}", ready_file.display()))?;
+ let main_script = find_main_script(&app_dir.join("app"))?;
- // Release lock
- lock_file
- .unlock()
+ // The ready marker doubles as the cache of the resolved main script. Write it last so a
+ // partially extracted directory is never mistaken for a usable one.
+ fs::write(&ready_path, &main_script)
+ .with_context(|| format!("Failed to create ready file at {}", ready_path.display()))?;
+
+ FileExt::unlock(&lock_file)
.context("Failed to release extraction lock")?;
- // Run the application
- run_app(&app_dir, &args[1..])
+ run_app(&app_dir, &main_script, &args[1..])?;
+
+ // `run_app` only returns when it could not start Node at all.
+ Err(anyhow::anyhow!(
+ "Failed to execute Node.js application from {}",
+ app_dir.display()
+ ))
}
-fn get_cache_dir() -> Result {
- let cache_dir = BaseDirs::new().unwrap().cache_dir().join("banderole");
- fs::create_dir_all(&cache_dir).context("Failed to create cache directory")?;
- Ok(cache_dir)
+fn get_cache_dir_fast() -> Result {
+ // Deliberately does not create the directory: the warm path never needs it to exist, and
+ // create_dir_all costs a syscall on every launch.
+ let base = BaseDirs::new().context("Failed to determine base directories")?;
+ Ok(base.cache_dir().join("banderole"))
}
fn get_node_executable_path(app_dir: &Path) -> PathBuf {
let node_dir = app_dir.join("node");
if cfg!(windows) {
- // Prefer common locations first
- let candidates = [node_dir.join("node.exe")];
- for c in candidates {
- if c.exists() {
- return c;
- }
- }
-
- // Recursively search for node.exe under node/
- if node_dir.exists() {
- for entry in walkdir::WalkDir::new(&node_dir).follow_links(true) {
- if let Ok(e) = entry {
- let p = e.path();
- if p.is_file() {
- if let Some(name) = p.file_name().and_then(|n| n.to_str()) {
- if name.eq_ignore_ascii_case("node.exe") {
- return p.to_path_buf();
- }
- }
- }
- }
- }
- }
-
- // Fallback: default where Windows Node is usually at after extraction
node_dir.join("node.exe")
} else {
- // On Unix systems, Node.js is in node/bin/node
- let candidate = node_dir.join("bin").join("node");
- if candidate.exists() {
- candidate
- } else {
- // As a last resort, search recursively
- if node_dir.exists() {
- for entry in walkdir::WalkDir::new(&node_dir).follow_links(true) {
- if let Ok(e) = entry {
- let p = e.path();
- if p.is_file() {
- if let Some(name) = p.file_name().and_then(|n| n.to_str()) {
- if name == "node" {
- return p.to_path_buf();
- }
- }
- }
- }
- }
- }
- candidate
- }
+ node_dir.join("bin").join("node")
}
}
-fn is_extraction_valid(app_dir: &Path) -> Result {
- let app_package_json = app_dir.join("app").join("package.json");
- let node_executable = get_node_executable_path(app_dir);
- #[cfg(windows)]
- let node_executable = node_executable
- .canonicalize()
- .unwrap_or_else(|_| node_executable.clone());
-
- let package_exists = app_package_json.exists();
- let node_exists = node_executable.exists();
-
- if !package_exists || !node_exists {
- // Log debugging information for failed validation
- eprintln!("Extraction validation failed:");
- eprintln!(" App directory: {}", app_dir.display());
- eprintln!(
- " Package.json exists: {} ({})",
- package_exists,
- app_package_json.display()
- );
- eprintln!(
- " Node executable exists: {} ({})",
- node_exists,
- node_executable.display()
- );
-
- if let Ok(entries) = fs::read_dir(app_dir) {
- eprintln!(" App directory contents:");
- for entry in entries.flatten() {
- eprintln!(" - {}", entry.file_name().to_string_lossy());
- }
- }
-
- if let Ok(entries) = fs::read_dir(app_dir.join("node")) {
- eprintln!(" Node directory contents:");
- for entry in entries.flatten() {
- eprintln!(" - {}", entry.file_name().to_string_lossy());
- }
- }
- }
-
- Ok(package_exists && node_exists)
-}
-
-struct FileEntry {
- path: PathBuf,
- data: Vec,
- #[cfg(unix)]
- mode: Option,
-}
-
-struct DirEntry {
- path: PathBuf,
-}
-
fn extract_application(app_dir: &Path) -> Result<()> {
// Remove existing directory if it exists to ensure clean extraction
if app_dir.exists() {
fs::remove_dir_all(app_dir).context("Failed to remove existing app directory")?;
}
- // Create app directory
fs::create_dir_all(app_dir).context("Failed to create app directory")?;
- // Read ZIP data directly from embedded bytes (no XZ decompression needed)
- let cursor = Cursor::new(ZIP_DATA);
- let mut archive = ZipArchive::new(cursor).context("Failed to open embedded zip archive")?;
-
- // First pass: collect all entries from the ZIP archive
- let mut files = Vec::new();
- let mut dirs = Vec::new();
-
- for i in 0..archive.len() {
- let mut file = archive.by_index(i).context("Failed to read zip entry")?;
-
- // Get the file name from the zip entry (clone to owned String to avoid borrow issues)
- let file_name = file.name().to_string();
-
- // Skip entries with invalid characters or paths
- if file_name.is_empty() || file_name.contains('\0') {
- continue;
- }
-
- // Determine if this is a directory entry
- let is_directory = file_name.ends_with('/') || file.is_dir();
-
- // Skip empty directory entries that are just the trailing slash
- if is_directory && (file_name == "/" || file_name.trim_matches('/').is_empty()) {
- continue;
- }
-
- // Remove trailing slash for proper path construction
- let clean_file_name = if is_directory {
- file_name.trim_end_matches('/').to_string()
- } else {
- file_name.clone()
- };
-
- // Skip if the cleaned name is empty (shouldn't happen but be safe)
- if clean_file_name.is_empty() {
- continue;
- }
-
- // Use proper path handling instead of string replacement
- // Split the path by forward slashes and join using PathBuf for proper platform handling
- let path_components: Vec<&str> = clean_file_name
- .split('/')
- .filter(|s| !s.is_empty())
- .collect();
-
- // Skip if no valid path components
- if path_components.is_empty() {
- continue;
- }
-
- let mut outpath = app_dir.to_path_buf();
- for component in path_components {
- outpath = outpath.join(component);
- }
-
- // Ensure the path is within the app directory (security check)
- if !outpath.starts_with(app_dir) {
- continue;
- }
-
- if is_directory {
- dirs.push(DirEntry { path: outpath });
- } else {
- // Read file data into memory
- let mut data = Vec::new();
- std::io::copy(&mut file, &mut data)
- .with_context(|| format!("Failed to read file data from {}", file_name))?;
+ // Parsed exactly once. `ZipArchive` holds its central directory behind an `Arc`, so the
+ // per-worker clones below are O(1) — re-opening the archive per worker instead would
+ // re-parse every entry per thread, which is the difference between 110k and 1.7M entry
+ // parses on a large project.
+ let archive =
+ ZipArchive::new(Cursor::new(ZIP_DATA)).context("Failed to open embedded zip archive")?;
+ let len = archive.len();
+ // Resolve every entry's destination path up front (metadata only), so the write pass can
+ // run in parallel without touching the shared central directory.
+ let mut dirs: BTreeSet = BTreeSet::new();
+ let mut files: Vec = Vec::with_capacity(len);
+ {
+ let mut archive = archive.clone();
+ // Entries arrive in directory-walk order, so consecutive files nearly always share a
+ // parent. Remembering the last one turns ~100k set insertions into ~10k.
+ let mut last_parent: Option = None;
+ for index in 0..len {
+ let entry = archive
+ .by_index_raw(index)
+ .context("Failed to read zip entry")?;
+ let is_dir = entry.is_dir() || entry.name().ends_with('/');
+ let Some(path) = safe_join(app_dir, entry.name()) else {
+ continue;
+ };
+ let size = entry.size();
#[cfg(unix)]
- let mode = file.unix_mode();
+ let mode = entry.unix_mode();
+ drop(entry);
- files.push(FileEntry {
- path: outpath,
- data,
+ if is_dir {
+ dirs.insert(path);
+ continue;
+ }
+
+ if let Some(parent) = path.parent() {
+ // Archives do not reliably carry directory entries, so every file's parent is
+ // recorded too.
+ if last_parent.as_deref() != Some(parent) {
+ dirs.insert(parent.to_path_buf());
+ last_parent = Some(parent.to_path_buf());
+ }
+ }
+ files.push(PlannedFile {
+ index,
+ path,
+ size,
#[cfg(unix)]
mode,
});
}
}
- // Second pass: create all directories (must be sequential to avoid conflicts)
- for dir in dirs {
- fs::create_dir_all(&dir.path)
- .with_context(|| format!("Failed to create directory '{}'", dir.path.display()))?;
+ // Sorted order means a parent is always created before its children, and `create_dir_all`
+ // then short-circuits on the already-existing prefix.
+ for dir in &dirs {
+ fs::create_dir_all(dir)
+ .with_context(|| format!("Failed to create directory '{}'", dir.display()))?;
}
- // Third pass: write all files in parallel for maximum speed
- files.par_iter().try_for_each(|entry| -> Result<()> {
- // Create parent directories first
- if let Some(parent) = entry.path.parent() {
- fs::create_dir_all(parent).with_context(|| {
- format!(
- "Failed to create parent directory '{}' for file '{}'",
- parent.display(),
- entry.path.display()
- )
- })?;
+ // Largest first. The bundler appends the ~105 MB Node binary as the *last* entry, so any
+ // static split of the work list hands it to one worker that is still writing long after
+ // the rest have finished. Longest-job-first plus a shared cursor keeps every core busy to
+ // the end.
+ files.sort_unstable_by(|a, b| b.size.cmp(&a.size));
+
+ let threads = std::thread::available_parallelism()
+ .map(|n| n.get())
+ .unwrap_or(4)
+ .min(files.len().max(1));
+ let next = AtomicUsize::new(0);
+
+ let result: Result<()> = std::thread::scope(|scope| {
+ let mut handles = Vec::with_capacity(threads);
+ for _ in 0..threads {
+ let mut archive = archive.clone();
+ let files = &files;
+ let next = &next;
+ handles.push(scope.spawn(move || -> Result<()> {
+ // Reused across entries so the common small-file case is one allocation per
+ // worker rather than one per file.
+ let mut buf: Vec = Vec::new();
+ loop {
+ let i = next.fetch_add(1, Ordering::Relaxed);
+ let Some(planned) = files.get(i) else { break };
+ let path = &planned.path;
+
+ let mut entry = archive
+ .by_index(planned.index)
+ .context("Failed to read zip entry")?;
+ let mut out = fs::File::create(path)
+ .with_context(|| format!("Failed to create file {}", path.display()))?;
+
+ if planned.size <= SMALL_ENTRY_BYTES {
+ buf.clear();
+ buf.reserve(planned.size as usize);
+ entry
+ .read_to_end(&mut buf)
+ .with_context(|| format!("Failed to read {}", path.display()))?;
+ out.write_all(&buf).with_context(|| {
+ format!("Failed to write file to {}", path.display())
+ })?;
+ } else {
+ // A big entry through io::copy's 8 KiB loop is tens of thousands of
+ // write syscalls; buffer it instead.
+ let mut writer = std::io::BufWriter::with_capacity(1 << 20, &mut out);
+ std::io::copy(&mut entry, &mut writer).with_context(|| {
+ format!("Failed to write file to {}", path.display())
+ })?;
+ writer.flush().with_context(|| {
+ format!("Failed to flush file {}", path.display())
+ })?;
+ }
+
+ #[cfg(unix)]
+ {
+ if let Some(mode) = planned.mode {
+ use std::os::unix::fs::PermissionsExt;
+ // fchmod on the open descriptor: no second path resolution, and
+ // identical semantics to the previous path-based set_permissions.
+ out.set_permissions(std::fs::Permissions::from_mode(mode))
+ .with_context(|| {
+ format!("Failed to set permissions on {}", path.display())
+ })?;
+ }
+ }
+ }
+ Ok(())
+ }));
}
-
- // Write file
- fs::write(&entry.path, &entry.data)
- .with_context(|| format!("Failed to write file to {}", entry.path.display()))?;
-
- // Set executable permissions on Unix systems
- #[cfg(unix)]
- {
- if let Some(mode) = entry.mode {
- use std::os::unix::fs::PermissionsExt;
- let permissions = std::fs::Permissions::from_mode(mode);
- fs::set_permissions(&entry.path, permissions).with_context(|| {
- format!("Failed to set permissions on {}", entry.path.display())
- })?;
- }
+ for handle in handles {
+ handle
+ .join()
+ .map_err(|_| anyhow::anyhow!("extraction worker panicked"))??;
}
-
Ok(())
- })?;
+ });
+ result?;
Ok(())
}
-fn run_app(app_dir: &Path, args: &[String]) -> Result<()> {
+/// Join a zip entry name onto `root`, rejecting absolute paths and traversal escapes.
+///
+/// Both `/` and `\` are treated as separators regardless of host platform. Zip names are
+/// specified to use `/`, but `PathBuf::push` also splits on `\` under Windows — so accepting
+/// a backslash as an ordinary character here would let an entry named `a\..\..\evil` escape
+/// `root` on Windows while passing a component-wise `starts_with` check.
+fn safe_join(root: &Path, name: &str) -> Option {
+ if name.is_empty() || name.contains('\0') {
+ return None;
+ }
+
+ let mut out = root.to_path_buf();
+ let mut pushed = false;
+ for component in name
+ .split(['/', '\\'])
+ .filter(|s| !s.is_empty() && *s != ".")
+ {
+ if component == ".." {
+ return None;
+ }
+ // Reject anything Windows would reinterpret: drive-relative prefixes, and trailing
+ // dots or spaces which the Win32 layer silently strips.
+ if component.contains(':') || component.ends_with('.') || component.ends_with(' ') {
+ return None;
+ }
+ out.push(component);
+ pushed = true;
+ }
+
+ if !pushed || !out.starts_with(root) {
+ return None;
+ }
+ Some(out)
+}
+
+/// Start the bundled Node.js app. On Unix this *replaces* the current process, so it only
+/// returns if Node could not be started at all.
+fn run_app(app_dir: &Path, main_script: &str, args: &[std::ffi::OsString]) -> Result<()> {
let app_path = app_dir.join("app");
let node_executable = get_node_executable_path(app_dir);
- // Verify Node.js executable exists and is accessible
- if !node_executable.exists() {
- let app_dir_contents = fs::read_dir(&app_dir)
- .map(|entries| {
- entries
- .filter_map(|e| e.ok())
- .map(|entry| entry.file_name().to_string_lossy().to_string())
- .collect::>()
- })
- .unwrap_or_else(|e| vec![format!("Error reading app dir: {}", e)]);
+ // Returning from here leaves the caller free to wipe and re-extract `app_dir`, so the
+ // process must not be left with its cwd inside that directory.
+ let previous_cwd = env::current_dir().ok();
+ let restore_cwd = || {
+ if let Some(cwd) = previous_cwd.as_ref() {
+ let _ = env::set_current_dir(cwd);
+ }
+ };
- let node_dir_contents = fs::read_dir(app_dir.join("node"))
- .map(|entries| {
- entries
- .filter_map(|e| e.ok())
- .map(|entry| entry.file_name().to_string_lossy().to_string())
- .collect::>()
- })
- .unwrap_or_else(|e| vec![format!("Error reading node dir: {}", e)]);
+ if env::set_current_dir(&app_path).is_err() {
+ return Ok(());
+ }
- return Err(anyhow::anyhow!(
- "Node.js executable not found at: {}\nPlatform: {} {}\nApp directory contents: {:?}\nNode directory contents: {:?}",
- node_executable.display(),
- std::env::consts::OS,
- std::env::consts::ARCH,
- app_dir_contents,
- node_dir_contents
- ));
+ let mut command = Command::new(&node_executable);
+ command.arg(main_script).args(args);
+
+ // Persist V8's compiled bytecode next to the extracted app so it survives between runs.
+ // This is what closes the gap with bundlers that ship precompiled bytecode: without it
+ // every launch recompiles the app's JavaScript, and the cost grows with dependency
+ // count. Node < 22.1 simply ignores the variable, and a caller that sets it explicitly
+ // keeps their own choice.
+ if env::var_os("NODE_COMPILE_CACHE").is_none() {
+ command.env("NODE_COMPILE_CACHE", app_dir.join(".v8cache"));
+ }
+
+ #[cfg(unix)]
+ {
+ use std::os::unix::process::CommandExt;
+ // Replaces this process image with Node: no fork, no resident parent holding the
+ // launcher's address space for the lifetime of the app, and signals/exit status are
+ // delivered by the kernel directly to Node.
+ let err = command.exec();
+ // exec() only returns on failure.
+ restore_cwd();
+ if node_executable.exists() {
+ return Err(anyhow::anyhow!(err).context(format!(
+ "Failed to execute Node.js at {}",
+ node_executable.display()
+ )));
+ }
+ return Ok(());
}
- // On Windows, verify the executable is actually executable
#[cfg(windows)]
{
- if let Ok(metadata) = fs::metadata(&node_executable) {
- if !metadata.is_file() {
- return Err(anyhow::anyhow!(
- "Node.js executable path exists but is not a file: {}",
+ use std::process::Stdio;
+ let mut last_err: Option = None;
+ for attempt in 1..=8u32 {
+ match command
+ .stdin(Stdio::inherit())
+ .stdout(Stdio::inherit())
+ .stderr(Stdio::inherit())
+ .status()
+ {
+ Ok(status) => std::process::exit(status.code().unwrap_or(1)),
+ Err(e) => {
+ last_err = Some(e);
+ std::thread::sleep(std::time::Duration::from_millis(50 * attempt as u64));
+ }
+ }
+ }
+ restore_cwd();
+ if node_executable.exists() {
+ if let Some(e) = last_err {
+ return Err(anyhow::anyhow!(e).context(format!(
+ "Failed to execute Node.js at {}",
node_executable.display()
- ));
- }
- } else {
- return Err(anyhow::anyhow!(
- "Cannot read metadata for Node.js executable: {}",
- node_executable.display()
- ));
- }
- }
-
- // Verify app directory exists
- if !app_path.exists() {
- return Err(anyhow::anyhow!(
- "App directory not found at: {}",
- app_path.display()
- ));
- }
-
- // Change to app directory
- env::set_current_dir(&app_path)
- .with_context(|| format!("Failed to change to app directory: {}", app_path.display()))?;
-
- // Find main script from package.json
- let main_script = find_main_script(&app_path)?;
-
- // Build command arguments
- let mut cmd_args = vec![main_script.clone()];
- cmd_args.extend(args.iter().cloned());
-
- let mut last_err: Option = None;
- let max_attempts: u32 = 8;
- let mut status: Option = None;
- for attempt in 1..=max_attempts {
- let status_res = Command::new(&node_executable)
- .args(&cmd_args)
- .stdin(Stdio::inherit())
- .stdout(Stdio::inherit())
- .stderr(Stdio::inherit())
- .status();
- match status_res {
- Ok(s) => {
- status = Some(s);
- break;
- }
- Err(e) => {
- last_err = Some(anyhow::anyhow!(e).context(format!(
- "Failed to execute Node.js application (attempt {attempt}/{max_attempts})\nExecutable: {}\nMain script: {}\nArgs: {:?}\nWorking directory: {}",
- node_executable.display(),
- main_script,
- cmd_args,
- app_path.display()
)));
- #[cfg(windows)]
- {
- use std::time::Duration;
- std::thread::sleep(Duration::from_millis(50 * attempt as u64));
- }
- #[cfg(not(windows))]
- {
- if attempt >= 2 {
- break;
- }
- }
}
}
+ Ok(())
}
- let status = status.ok_or_else(|| {
- last_err.unwrap_or_else(|| {
- anyhow::anyhow!(
- "Failed to execute Node.js application after {} attempts",
- max_attempts
- )
- })
- })?;
-
- std::process::exit(status.code().unwrap_or(1));
}
fn find_main_script(app_path: &Path) -> Result {
let package_json_path = app_path.join("package.json");
- if package_json_path.exists() {
- let package_content =
- fs::read_to_string(&package_json_path).context("Failed to read package.json")?;
-
- if let Ok(package_json) = serde_json::from_str::(&package_content) {
+ if let Ok(content) = fs::read_to_string(&package_json_path) {
+ if let Ok(package_json) = serde_json::from_str::(&content) {
if let Some(main) = package_json["main"].as_str() {
- return Ok(main.to_string());
+ if !main.trim().is_empty() {
+ return Ok(main.to_string());
+ }
}
}
}
- // Default to index.js
Ok("index.js".to_string())
}
diff --git a/tests/common/mod.rs b/tests/common/mod.rs
index 9efa7a8..6a22b2a 100644
--- a/tests/common/mod.rs
+++ b/tests/common/mod.rs
@@ -816,7 +816,7 @@ impl BundlerTestHelper {
exec_to_run.display(),
args,
env_vars,
- &work_dir
+ work_dir
)
})?;
Ok(output)
@@ -862,24 +862,24 @@ impl BundlerTestHelper {
pub struct TestCacheManager;
impl TestCacheManager {
+ /// The directory the launcher extracts bundles into, matching `directories::BaseDirs`.
+ pub fn application_cache_dir() -> PathBuf {
+ if cfg!(windows) {
+ std::env::var_os("LOCALAPPDATA")
+ .map(|d| PathBuf::from(d).join("banderole"))
+ .unwrap_or_else(|| PathBuf::from("banderole-cache"))
+ } else if let Some(xdg_cache) = std::env::var_os("XDG_CACHE_HOME") {
+ PathBuf::from(xdg_cache).join("banderole")
+ } else if let Some(home) = std::env::var_os("HOME") {
+ PathBuf::from(home).join(".cache").join("banderole")
+ } else {
+ PathBuf::from("/tmp").join("banderole-cache")
+ }
+ }
+
/// Clear application cache for testing
pub fn clear_application_cache() -> Result<()> {
- // Determine cache directory based on platform
- let cache_dir = if cfg!(windows) {
- if let Some(local_app_data) = std::env::var_os("LOCALAPPDATA") {
- std::path::PathBuf::from(local_app_data).join("banderole")
- } else {
- return Ok(()); // Can't determine cache dir, skip cleanup
- }
- } else if let Some(xdg_cache) = std::env::var_os("XDG_CACHE_HOME") {
- std::path::PathBuf::from(xdg_cache).join("banderole")
- } else if let Some(home) = std::env::var_os("HOME") {
- std::path::PathBuf::from(home)
- .join(".cache")
- .join("banderole")
- } else {
- std::path::PathBuf::from("/tmp").join("banderole-cache")
- };
+ let cache_dir = Self::application_cache_dir();
if cache_dir.exists() {
println!("Clearing application cache at: {}", cache_dir.display());
diff --git a/tests/launcher_semantics_integration_test.rs b/tests/launcher_semantics_integration_test.rs
new file mode 100644
index 0000000..e19e13b
--- /dev/null
+++ b/tests/launcher_semantics_integration_test.rs
@@ -0,0 +1,218 @@
+//! Covers the launcher behaviour introduced when the runtime switched from
+//! "spawn Node as a child and wait" to "exec() into Node", and when the payload
+//! was reduced to just the Node executable.
+
+mod common;
+
+use common::{BundlerTestHelper, TestCacheManager};
+use serial_test::serial;
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use tempfile::TempDir;
+
+/// Writes a project whose entrypoint can exit with a chosen code or idle forever.
+fn write_probe_project(root: &Path) -> Result> {
+ let app = root.join("probe-app");
+ fs::create_dir_all(&app)?;
+
+ fs::write(
+ app.join("package.json"),
+ r#"{
+ "name": "probe-app",
+ "version": "1.0.0",
+ "main": "index.js"
+}"#,
+ )?;
+
+ fs::write(
+ app.join("index.js"),
+ r#"
+const mode = process.argv[2] || "hello";
+if (mode === "exit") {
+ process.exit(Number(process.argv[3] || 0));
+} else if (mode === "idle") {
+ console.log("ready");
+ setInterval(() => {}, 1000);
+} else if (mode === "args") {
+ console.log(JSON.stringify(process.argv.slice(2)));
+} else {
+ console.log("probe ok");
+}
+"#,
+ )?;
+
+ Ok(app)
+}
+
+/// The launcher must hand the process over to Node rather than supervising it, so exit
+/// codes come straight from the app and no launcher process stays resident.
+#[tokio::test(flavor = "multi_thread")]
+#[serial]
+async fn test_launcher_execs_into_node() -> Result<(), Box> {
+ let temp_dir = TempDir::new()?;
+ let app = write_probe_project(temp_dir.path())?;
+ let out_dir = temp_dir.path().join("out");
+ fs::create_dir_all(&out_dir)?;
+
+ TestCacheManager::clear_application_cache()?;
+ let exe = BundlerTestHelper::bundle_project(&app, &out_dir, Some("probe-app"))?;
+
+ // First launch performs the extraction; second launch takes the warm path. Both must
+ // behave identically.
+ for pass in ["cold", "warm"] {
+ let out = BundlerTestHelper::run_executable(&exe, &[], &[])?;
+ assert!(
+ out.status.success(),
+ "{pass} launch failed: {}",
+ String::from_utf8_lossy(&out.stderr)
+ );
+ assert!(
+ String::from_utf8_lossy(&out.stdout).contains("probe ok"),
+ "{pass} launch produced unexpected stdout: {}",
+ String::from_utf8_lossy(&out.stdout)
+ );
+ }
+
+ // Arbitrary non-zero exit codes must survive. Under the old spawn+wait launcher this
+ // went through `status.code().unwrap_or(1)`; under exec() the kernel reports it directly.
+ for code in [0, 3, 42] {
+ let out = BundlerTestHelper::run_executable(&exe, &["exit", &code.to_string()], &[])?;
+ assert_eq!(
+ out.status.code(),
+ Some(code),
+ "exit code {code} was not propagated"
+ );
+ }
+
+ // Arguments after the executable name must reach the script untouched.
+ let out = BundlerTestHelper::run_executable(&exe, &["args", "--flag", "value"], &[])?;
+ let stdout = String::from_utf8_lossy(&out.stdout);
+ assert!(
+ stdout.contains("\"--flag\"") && stdout.contains("\"value\""),
+ "arguments were not forwarded: {stdout}"
+ );
+
+ Ok(())
+}
+
+/// The launcher process must be *replaced* by Node, not remain as its parent: a resident
+/// parent doubles the process count and the memory held while the app is idle or sleeping.
+#[cfg(unix)]
+#[tokio::test(flavor = "multi_thread")]
+#[serial]
+async fn test_no_launcher_process_remains_resident() -> Result<(), Box> {
+ use std::io::Read;
+ use std::process::Stdio;
+
+ let temp_dir = TempDir::new()?;
+ let app = write_probe_project(temp_dir.path())?;
+ let out_dir = temp_dir.path().join("out");
+ fs::create_dir_all(&out_dir)?;
+
+ TestCacheManager::clear_application_cache()?;
+ let exe = BundlerTestHelper::bundle_project(&app, &out_dir, Some("probe-app"))?;
+
+ // Warm the cache so the timing below is not racing the first-run extraction.
+ BundlerTestHelper::run_executable(&exe, &[], &[])?;
+
+ let mut child = Command::new(&exe)
+ .arg("idle")
+ .stdout(Stdio::piped())
+ .stderr(Stdio::null())
+ .spawn()?;
+
+ // Wait for the app to announce itself so we know exec() has already happened.
+ let mut stdout = child.stdout.take().expect("piped stdout");
+ let mut buf = [0u8; 32];
+ let n = stdout.read(&mut buf)?;
+ assert!(
+ String::from_utf8_lossy(&buf[..n]).contains("ready"),
+ "idle app never became ready"
+ );
+
+ // Sample /proc *before* asserting anything, then always reap the child, so a failed
+ // assertion cannot leave an idle Node process behind.
+ let pid = child.id();
+ let children =
+ fs::read_to_string(format!("/proc/{pid}/task/{pid}/children")).unwrap_or_default();
+ let comm = fs::read_to_string(format!("/proc/{pid}/comm")).ok();
+
+ child.kill().ok();
+ child.wait().ok();
+
+ // The spawned pid itself is now Node, so it must have no children of its own.
+ assert!(
+ children.trim().is_empty(),
+ "launcher still has child processes ({children:?}); it did not exec into Node"
+ );
+ if let Some(comm) = comm {
+ assert_eq!(
+ comm.trim(),
+ "node",
+ "spawned process should have been replaced by Node"
+ );
+ }
+
+ Ok(())
+}
+
+/// Only the Node executable belongs in the payload. Shipping the whole distribution added
+/// ~65 MB of headers, npm and docs that the launcher can never reach.
+#[tokio::test(flavor = "multi_thread")]
+#[serial]
+async fn test_payload_contains_only_node_executable() -> Result<(), Box> {
+ let temp_dir = TempDir::new()?;
+ let app = write_probe_project(temp_dir.path())?;
+ let out_dir = temp_dir.path().join("out");
+ fs::create_dir_all(&out_dir)?;
+
+ TestCacheManager::clear_application_cache()?;
+ let exe = BundlerTestHelper::bundle_project(&app, &out_dir, Some("probe-app"))?;
+
+ // A full Node distribution is ~186 MB uncompressed; the compressed single binary is
+ // well under a third of that. The bound is deliberately loose so ordinary Node version
+ // drift does not make this flaky.
+ let size = fs::metadata(&exe)?.len();
+ assert!(
+ size < 80 * 1024 * 1024,
+ "bundle is {size} bytes; expected well under 80 MB, so the payload is likely \
+ carrying the whole Node distribution again"
+ );
+
+ // Run it so the cache directory is populated, then inspect what actually landed there.
+ let out = BundlerTestHelper::run_executable(&exe, &[], &[])?;
+ assert!(out.status.success());
+
+ let cache_root = TestCacheManager::application_cache_dir();
+ let app_dir = fs::read_dir(&cache_root)?
+ .filter_map(|e| e.ok())
+ .map(|e| e.path())
+ .find(|p| p.is_dir() && p.join("node").exists())
+ .ok_or("no extracted bundle found in the cache directory")?;
+
+ let node_dir = app_dir.join("node");
+ let node_binary = if cfg!(windows) {
+ node_dir.join("node.exe")
+ } else {
+ node_dir.join("bin").join("node")
+ };
+ assert!(
+ node_binary.exists(),
+ "extracted bundle is missing the Node executable at {}",
+ node_binary.display()
+ );
+
+ for unwanted in ["include", "share"] {
+ assert!(
+ !node_dir.join(unwanted).exists(),
+ "extracted bundle still ships node/{unwanted}"
+ );
+ }
+ assert!(
+ !node_dir.join("lib").join("node_modules").exists(),
+ "extracted bundle still ships npm/corepack under node/lib/node_modules"
+ );
+
+ Ok(())
+}