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(()) +}