From fe9b0d86f40f9fd0c331ce8fabaa8b01b86381f3 Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Wed, 10 Dec 2025 17:54:49 -0500 Subject: [PATCH 01/43] ci: release candid_ui (#692) GItHub releases will include prebuilt `candid_ui.wasm` so that tools like `icp-cli` can integrate. A test release can be found [here](https://github.com/dfinity/candid/releases/tag/test_release_candid_ui_2). --- .github/workflows/release.yml | 57 ++++--- tools/ui/Cargo.lock | 309 ++++++++++++++++++++-------------- tools/ui/Cargo.toml | 7 +- tools/ui/dfx.json | 6 +- tools/ui/package-lock.json | 26 +-- 5 files changed, 242 insertions(+), 163 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f31bfe0b..a909b872d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: name: linux64 artifact_name: target/x86_64-unknown-linux-musl/release/didc asset_name: didc-linux64 - - os: macos-13-large + - os: macos-15-intel name: macos artifact_name: target/release/didc asset_name: didc-macos @@ -26,21 +26,11 @@ jobs: asset_name: didc-arm32 steps: - uses: actions/checkout@v4 - - name: Install stable toolchain - if: matrix.name != 'arm' - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: Install stable toolchain + - name: Install Rust toolchain + run: rustup show active-toolchain || rustup toolchain install + - name: Add arm target if: matrix.name == 'arm' - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - target: arm-unknown-linux-gnueabihf + run: rustup target add arm-unknown-linux-gnueabihf - name: Build if: matrix.name == 'linux64' run: | @@ -77,10 +67,6 @@ jobs: asset_name: didc-linux64 - os: ubuntu-22.04 asset_name: didc-linux64 - - os: macos-14-large - asset_name: didc-macos - - os: macos-13-large - asset_name: didc-macos steps: - name: Get executable id: download @@ -114,3 +100,36 @@ jobs: file: didc asset_name: ${{ matrix.asset_name }} tag: ${{ github.ref }} + + candid-ui: + name: Build and publish candid_ui canister + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install Rust toolchain + run: rustup show active-toolchain || rustup toolchain install + - name: Install binaryen + run: | + sudo apt-get update -yy + sudo apt-get install -yy binaryen + - name: Build candid_ui + working-directory: tools/ui + run: | + npm install + npm run build + cargo build --target wasm32-unknown-unknown --profile canister --package didjs + wasm-opt --strip-debug -Oz target/wasm32-unknown-unknown/canister/didjs.wasm -o target/wasm32-unknown-unknown/canister/didjs_opt.wasm + - name: Prepare asset + run: | + cp tools/ui/target/wasm32-unknown-unknown/canister/didjs_opt.wasm candid_ui.wasm + - name: Upload candid_ui to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: candid_ui.wasm + asset_name: candid_ui.wasm + tag: ${{ github.ref }} diff --git a/tools/ui/Cargo.lock b/tools/ui/Cargo.lock index 418d7950f..27cff3e08 100644 --- a/tools/ui/Cargo.lock +++ b/tools/ui/Cargo.lock @@ -1,27 +1,36 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "ar_archive_writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +dependencies = [ + "object", +] [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arrayvec" @@ -40,9 +49,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" @@ -96,9 +105,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -117,13 +126,13 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "candid" -version = "0.10.13" +version = "0.10.20" dependencies = [ "anyhow", "binread", @@ -144,12 +153,12 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.6.6" +version = "0.10.20" dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] @@ -173,18 +182,19 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.22" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "codespan-reporting" @@ -193,7 +203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -207,33 +217,33 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -241,9 +251,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "didjs" @@ -290,9 +300,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "ena" @@ -305,9 +315,15 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fixedbitset" @@ -333,9 +349,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -350,9 +366,9 @@ checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hex" @@ -373,17 +389,24 @@ dependencies = [ [[package]] name = "ic-cdk" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8ecacd682fa05a985253592963306cb9799622d7b1cce4b1edb89c6ec85be1" +checksum = "a50afd39a4c2022ca6cbdf15dbd653b33eeaae9010748b74006bbd717aa7c51a" dependencies = [ "candid", + "ic-cdk-executor", "ic-cdk-macros", "ic0", "serde", "serde_bytes", ] +[[package]] +name = "ic-cdk-executor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903057edd3d4ff4b3fe44a64eaee1ceb73f579ba29e3ded372b63d291d7c16c2" + [[package]] name = "ic-cdk-macros" version = "0.16.0" @@ -395,7 +418,7 @@ dependencies = [ "quote", "serde", "serde_tokenstream", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] @@ -454,9 +477,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -473,9 +496,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "lalrpop" @@ -491,7 +514,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.8", "string_cache", "term", "tiny-keccak", @@ -522,15 +545,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags", "libc", @@ -538,19 +561,18 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos" @@ -572,7 +594,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.6.29", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] @@ -586,9 +608,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "new_debug_unreachable" @@ -626,16 +648,19 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -643,15 +668,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -672,9 +697,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] @@ -693,47 +718,48 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55c4d17d994b637e2f4daf6e5dc5d660d209d5642377d675d7a1c3ab69fa579" +checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156" dependencies = [ "arrayvec", "typed-arena", - "unicode-width", + "unicode-width 0.2.2", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "psm" -version = "0.1.23" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" +checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" dependencies = [ + "ar_archive_writer", "cc", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.5.6" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -751,25 +777,25 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.4", + "regex-syntax 0.8.8", ] [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.8", ] [[package]] @@ -780,15 +806,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -807,20 +833,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.15" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -833,15 +861,24 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] @@ -853,14 +890,14 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -875,37 +912,36 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stacker" -version = "0.1.17" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" dependencies = [ "cc", "cfg-if", "libc", "psm", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] name = "string_cache" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", - "once_cell", "parking_lot", "phf_shared", "precomputed-hash", @@ -924,9 +960,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -955,22 +991,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.111", ] [[package]] @@ -990,15 +1026,15 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -1012,6 +1048,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -1042,9 +1084,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "winapi" @@ -1064,11 +1106,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1077,6 +1119,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.59.0" @@ -1086,6 +1134,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/tools/ui/Cargo.toml b/tools/ui/Cargo.toml index 417b27642..54191cf34 100644 --- a/tools/ui/Cargo.toml +++ b/tools/ui/Cargo.toml @@ -4,9 +4,10 @@ members = [ ] resolver = "2" -[profile.release] -lto = true -opt-level = 2 +[profile.canister] +inherits = "release" +opt-level = "z" +strip = "debuginfo" [patch.crates-io.candid] path = "../../rust/candid" diff --git a/tools/ui/dfx.json b/tools/ui/dfx.json index 5fa1ceef7..9125fc637 100644 --- a/tools/ui/dfx.json +++ b/tools/ui/dfx.json @@ -3,11 +3,11 @@ "didjs": { "type": "custom", "candid": "src/didjs/didjs.did", - "wasm": "target/wasm32-unknown-unknown/release/didjs_opt.wasm", + "wasm": "target/wasm32-unknown-unknown/canister/didjs.wasm", "build": [ "npm run build", - "cargo build --target wasm32-unknown-unknown --release --package didjs", - "wasm-opt --strip-debug -Oz target/wasm32-unknown-unknown/release/didjs.wasm -o target/wasm32-unknown-unknown/release/didjs_opt.wasm" + "cargo build --target wasm32-unknown-unknown --profile canister --package didjs", + "wasm-opt --strip-debug -Oz target/wasm32-unknown-unknown/canister/didjs.wasm -o target/wasm32-unknown-unknown/canister/didjs_opt.wasm" ], "metadata": [ { diff --git a/tools/ui/package-lock.json b/tools/ui/package-lock.json index 68498652f..effe61dea 100644 --- a/tools/ui/package-lock.json +++ b/tools/ui/package-lock.json @@ -989,10 +989,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1824,9 +1825,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -1834,6 +1835,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -3616,9 +3618,9 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -4216,9 +4218,9 @@ "dev": true }, "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true }, "neo-async": { From 04e34a860f7170c3ec27cb89079c5f79f50aa7ec Mon Sep 17 00:00:00 2001 From: Luca Bertelli Date: Thu, 18 Dec 2025 10:59:43 +0100 Subject: [PATCH 02/43] fix: escape `*/` to prevent premature JS doc comment termination (#693) **Overview** Cherry-pick of https://github.com/dfinity/icp-js-bindgen/pull/102. --- rust/candid_parser/src/bindings/typescript.rs | 13 ++++++--- .../tests/assets/malicious_doc.did | 13 +++++++++ .../tests/assets/ok/malicious_doc.d.ts | 26 ++++++++++++++++++ .../tests/assets/ok/malicious_doc.did | 12 +++++++++ .../tests/assets/ok/malicious_doc.js | 5 ++++ .../tests/assets/ok/malicious_doc.mo | 17 ++++++++++++ .../tests/assets/ok/malicious_doc.rs | 27 +++++++++++++++++++ 7 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 rust/candid_parser/tests/assets/malicious_doc.did create mode 100644 rust/candid_parser/tests/assets/ok/malicious_doc.d.ts create mode 100644 rust/candid_parser/tests/assets/ok/malicious_doc.did create mode 100644 rust/candid_parser/tests/assets/ok/malicious_doc.js create mode 100644 rust/candid_parser/tests/assets/ok/malicious_doc.mo create mode 100644 rust/candid_parser/tests/assets/ok/malicious_doc.rs diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index ce09e3754..3366d58cf 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -246,14 +246,19 @@ fn pp_service<'a>( enclose_space("{", concat(methods, ","), "}") } +/// Escapes doc comment content to prevent comment injection attacks. +/// Replaces `*/` with `*\/` to prevent premature comment termination. +fn escape_doc_comment(line: &str) -> String { + line.replace("*/", r"*\/") +} + fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { if docs.is_empty() { RcDoc::nil() } else { - let docs = lines( - docs.iter() - .map(|line| RcDoc::text(DOC_COMMENT_LINE_PREFIX).append(line)), - ); + let docs = lines(docs.iter().map(|line| { + RcDoc::text(DOC_COMMENT_LINE_PREFIX).append(RcDoc::text(escape_doc_comment(line))) + })); RcDoc::text(DOC_COMMENT_PREFIX) .append(RcDoc::hardline()) .append(docs) diff --git a/rust/candid_parser/tests/assets/malicious_doc.did b/rust/candid_parser/tests/assets/malicious_doc.did new file mode 100644 index 000000000..f8d586d00 --- /dev/null +++ b/rust/candid_parser/tests/assets/malicious_doc.did @@ -0,0 +1,13 @@ +// */ import { malicious } from 'attacker'; console.log('injected!'); /* +// Normal doc line +// Another */ attempt /* to inject +type MaliciousType = record { + // Doc comment for field with */ malicious code /* in it + field : text +}; + +// Service with */ malicious */ doc +service : { + // Method with */ in doc comment + get : () -> (MaliciousType) query +} diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts b/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts new file mode 100644 index 000000000..7241f8bd7 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts @@ -0,0 +1,26 @@ +import type { Principal } from '@dfinity/principal'; +import type { ActorMethod } from '@dfinity/agent'; +import type { IDL } from '@dfinity/candid'; + +/** + * *\/ import { malicious } from 'attacker'; console.log('injected!'); /* + * Normal doc line + * Another *\/ attempt /* to inject + */ +export interface MaliciousType { + /** + * Doc comment for field with *\/ malicious code /* in it + */ + 'field' : string, +} +/** + * Service with *\/ malicious *\/ doc + */ +export interface _SERVICE { + /** + * Method with *\/ in doc comment + */ + 'get' : ActorMethod<[], MaliciousType>, +} +export declare const idlFactory: IDL.InterfaceFactory; +export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.did b/rust/candid_parser/tests/assets/ok/malicious_doc.did new file mode 100644 index 000000000..714759924 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.did @@ -0,0 +1,12 @@ +// */ import { malicious } from 'attacker'; console.log('injected!'); /* +// Normal doc line +// Another */ attempt /* to inject +type MaliciousType = record { + // Doc comment for field with */ malicious code /* in it + field : text; +}; +// Service with */ malicious */ doc +service : { + // Method with */ in doc comment + get : () -> (MaliciousType) query; +} diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.js b/rust/candid_parser/tests/assets/ok/malicious_doc.js new file mode 100644 index 000000000..ff18f8ed1 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.js @@ -0,0 +1,5 @@ +export const idlFactory = ({ IDL }) => { + const MaliciousType = IDL.Record({ 'field' : IDL.Text }); + return IDL.Service({ 'get' : IDL.Func([], [MaliciousType], ['query']) }); +}; +export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.mo b/rust/candid_parser/tests/assets/ok/malicious_doc.mo new file mode 100644 index 000000000..046143246 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.mo @@ -0,0 +1,17 @@ +// This is a generated Motoko binding. +// Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. + +module { + /// */ import { malicious } from 'attacker'; console.log('injected!'); /* + /// Normal doc line + /// Another */ attempt /* to inject + public type MaliciousType = { + /// Doc comment for field with */ malicious code /* in it + field : Text; + }; + /// Service with */ malicious */ doc + public type Self = actor { + /// Method with */ in doc comment + get : shared query () -> async MaliciousType; + } +} diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.rs b/rust/candid_parser/tests/assets/ok/malicious_doc.rs new file mode 100644 index 000000000..5b4a5d48f --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.rs @@ -0,0 +1,27 @@ +// This is an experimental feature to generate Rust binding from Candid. +// You may want to manually adjust some of the types. +#![allow(dead_code, unused_imports)] +use candid::{self, CandidType, Deserialize, Principal}; +use ic_cdk::api::call::CallResult as Result; + +/// */ import { malicious } from 'attacker'; console.log('injected!'); /* +/// Normal doc line +/// Another */ attempt /* to inject +#[derive(CandidType, Deserialize)] +pub struct MaliciousType { + /// Doc comment for field with */ malicious code /* in it + pub field: String, +} + +/// Service with */ malicious */ doc +pub struct Service(pub Principal); +impl Service { + /// Method with */ in doc comment + pub async fn get(&self) -> Result<(MaliciousType,)> { + ic_cdk::call(self.0, "get", ()).await + } +} +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); +pub const service : Service = Service(CANISTER_ID); + From 2e4a2cf6a66593e2cb3289535899eb232635bef2 Mon Sep 17 00:00:00 2001 From: Luca Bertelli Date: Thu, 18 Dec 2025 15:45:43 +0100 Subject: [PATCH 03/43] chore: release candid_parser v0.2.4, didc v0.5.4 (#694) --- CHANGELOG.md | 7 +++++++ Cargo.lock | 8 ++++---- rust/bench/Cargo.lock | 2 +- rust/candid_parser/Cargo.toml | 2 +- tools/didc/Cargo.toml | 2 +- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3574d709..2b9726821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## 2025-12-18 + +### candid_parser 0.2.4 & didc 0.5.4 + +* Non-breaking changes: + + fix: escape `*/` to prevent premature JS doc comment termination + ## 2025-10-26 ### Candid 0.10.20 diff --git a/Cargo.lock b/Cargo.lock index 7f5d6a427..c8842c8ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,7 +216,7 @@ dependencies = [ "binread", "byteorder", "candid_derive 0.10.20", - "candid_parser 0.2.3", + "candid_parser 0.2.4", "hex", "ic_principal 0.1.1", "leb128", @@ -276,7 +276,7 @@ dependencies = [ [[package]] name = "candid_parser" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "arbitrary", @@ -479,10 +479,10 @@ dependencies = [ [[package]] name = "didc" -version = "0.5.3" +version = "0.5.4" dependencies = [ "anyhow", - "candid_parser 0.2.3", + "candid_parser 0.2.4", "clap", "console", "hex", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index ec8591f2d..0fed37da4 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -178,7 +178,7 @@ dependencies = [ [[package]] name = "candid_parser" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "candid", diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml index f7342923f..cb221692e 100644 --- a/rust/candid_parser/Cargo.toml +++ b/rust/candid_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid_parser" -version = "0.2.3" +version = "0.2.4" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] diff --git a/tools/didc/Cargo.toml b/tools/didc/Cargo.toml index b52511f3a..43fe2b382 100644 --- a/tools/didc/Cargo.toml +++ b/tools/didc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "didc" -version = "0.5.3" +version = "0.5.4" authors = ["DFINITY Team"] edition = "2021" From 7f7859de112a893730c0bf2f95d642cb553bc793 Mon Sep 17 00:00:00 2001 From: Vivienne Siffert Date: Wed, 7 Jan 2026 13:53:48 +0100 Subject: [PATCH 04/43] chore!: generate @icp-sdk/core imports (#695) Changed imports generated by `candid_parser::bindings::typescript::compile` from `@dfinity/*` to `@icp-sdk/core/*` --- CHANGELOG.md | 3 +++ rust/candid_parser/src/bindings/typescript.rs | 6 +++--- rust/candid_parser/tests/assets/ok/actor.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/class.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/comment.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/cyclic.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/empty.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/escape.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/example.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/fieldnat.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/inline_methods.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/keyword.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/malicious_doc.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/management.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/recursion.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/recursive_class.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/service.d.ts | 6 +++--- rust/candid_parser/tests/assets/ok/unicode.d.ts | 6 +++--- 18 files changed, 54 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9726821..7cf1aeef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Breaking changes: + + Changed imports generated by `candid_parser::bindings::typescript::compile` from `@dfinity/*` to `@icp-sdk/core/*` + ## 2025-12-18 ### candid_parser 0.2.4 & didc 0.5.4 diff --git a/rust/candid_parser/src/bindings/typescript.rs b/rust/candid_parser/src/bindings/typescript.rs index 3366d58cf..673d80488 100644 --- a/rust/candid_parser/src/bindings/typescript.rs +++ b/rust/candid_parser/src/bindings/typescript.rs @@ -324,9 +324,9 @@ fn pp_actor<'a>(env: &'a TypeEnv, ty: &'a Type, syntax: Option<&'a IDLType>) -> } pub fn compile(env: &TypeEnv, actor: &Option, prog: &IDLMergedProg) -> String { - let header = r#"import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; + let header = r#"import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; "#; let syntax_actor = prog.resolve_actor().ok().flatten(); let def_list: Vec<_> = env.0.iter().map(|pair| pair.0.as_ref()).collect(); diff --git a/rust/candid_parser/tests/assets/ok/actor.d.ts b/rust/candid_parser/tests/assets/ok/actor.d.ts index 780b66b7c..ce5b7ef2b 100644 --- a/rust/candid_parser/tests/assets/ok/actor.d.ts +++ b/rust/candid_parser/tests/assets/ok/actor.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type f = ActorMethod<[number], number>; export type g = f; diff --git a/rust/candid_parser/tests/assets/ok/class.d.ts b/rust/candid_parser/tests/assets/ok/class.d.ts index acd196f00..9a35748bc 100644 --- a/rust/candid_parser/tests/assets/ok/class.d.ts +++ b/rust/candid_parser/tests/assets/ok/class.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type List = [] | [[bigint, List]]; export interface Profile { 'age' : number, 'name' : string } diff --git a/rust/candid_parser/tests/assets/ok/comment.d.ts b/rust/candid_parser/tests/assets/ok/comment.d.ts index ff7d3a185..7e4bc2ee7 100644 --- a/rust/candid_parser/tests/assets/ok/comment.d.ts +++ b/rust/candid_parser/tests/assets/ok/comment.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; /** * line comment diff --git a/rust/candid_parser/tests/assets/ok/cyclic.d.ts b/rust/candid_parser/tests/assets/ok/cyclic.d.ts index d75b132a3..908e3e263 100644 --- a/rust/candid_parser/tests/assets/ok/cyclic.d.ts +++ b/rust/candid_parser/tests/assets/ok/cyclic.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type A = [] | [B]; export type B = [] | [C]; diff --git a/rust/candid_parser/tests/assets/ok/empty.d.ts b/rust/candid_parser/tests/assets/ok/empty.d.ts index e01c5afbf..315885bfb 100644 --- a/rust/candid_parser/tests/assets/ok/empty.d.ts +++ b/rust/candid_parser/tests/assets/ok/empty.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type T = [T]; export interface _SERVICE { diff --git a/rust/candid_parser/tests/assets/ok/escape.d.ts b/rust/candid_parser/tests/assets/ok/escape.d.ts index 54eaf207c..0908479b7 100644 --- a/rust/candid_parser/tests/assets/ok/escape.d.ts +++ b/rust/candid_parser/tests/assets/ok/escape.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export interface t { '\"' : bigint, diff --git a/rust/candid_parser/tests/assets/ok/example.d.ts b/rust/candid_parser/tests/assets/ok/example.d.ts index dcdc5520b..de431ffb9 100644 --- a/rust/candid_parser/tests/assets/ok/example.d.ts +++ b/rust/candid_parser/tests/assets/ok/example.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type A = B; export type B = [] | [A]; diff --git a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts index 53e68afe1..ec5bad50f 100644 --- a/rust/candid_parser/tests/assets/ok/fieldnat.d.ts +++ b/rust/candid_parser/tests/assets/ok/fieldnat.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export interface non_tuple { _1_ : string, _2_ : string } export type tuple = [string, string]; diff --git a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts index 687fa6900..cf3951e2c 100644 --- a/rust/candid_parser/tests/assets/ok/inline_methods.d.ts +++ b/rust/candid_parser/tests/assets/ok/inline_methods.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type Fn = ActorMethod<[bigint], bigint>; export type Gn = Fn; diff --git a/rust/candid_parser/tests/assets/ok/keyword.d.ts b/rust/candid_parser/tests/assets/ok/keyword.d.ts index 22d1c24a6..8e2c0aa6c 100644 --- a/rust/candid_parser/tests/assets/ok/keyword.d.ts +++ b/rust/candid_parser/tests/assets/ok/keyword.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type if_ = { 'branch' : { 'val' : bigint, 'left' : if_, 'right' : if_ } diff --git a/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts b/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts index 7241f8bd7..f613779f8 100644 --- a/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts +++ b/rust/candid_parser/tests/assets/ok/malicious_doc.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; /** * *\/ import { malicious } from 'attacker'; console.log('injected!'); /* diff --git a/rust/candid_parser/tests/assets/ok/management.d.ts b/rust/candid_parser/tests/assets/ok/management.d.ts index ca60e354d..8cf54f90a 100644 --- a/rust/candid_parser/tests/assets/ok/management.d.ts +++ b/rust/candid_parser/tests/assets/ok/management.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type bitcoin_address = string; export type bitcoin_network = { 'mainnet' : null } | diff --git a/rust/candid_parser/tests/assets/ok/recursion.d.ts b/rust/candid_parser/tests/assets/ok/recursion.d.ts index 3404ebcce..2f0a0f626 100644 --- a/rust/candid_parser/tests/assets/ok/recursion.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursion.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type A = B; export type B = [] | [A]; diff --git a/rust/candid_parser/tests/assets/ok/recursive_class.d.ts b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts index ba2ef3fda..546910492 100644 --- a/rust/candid_parser/tests/assets/ok/recursive_class.d.ts +++ b/rust/candid_parser/tests/assets/ok/recursive_class.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export interface s { 'next' : ActorMethod<[], Principal> } export interface _SERVICE extends s {} diff --git a/rust/candid_parser/tests/assets/ok/service.d.ts b/rust/candid_parser/tests/assets/ok/service.d.ts index 57c3c871d..aea67cd85 100644 --- a/rust/candid_parser/tests/assets/ok/service.d.ts +++ b/rust/candid_parser/tests/assets/ok/service.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export type Func = ActorMethod<[], Principal>; export interface Service { 'f' : Func } diff --git a/rust/candid_parser/tests/assets/ok/unicode.d.ts b/rust/candid_parser/tests/assets/ok/unicode.d.ts index 1e559610c..e9833d02e 100644 --- a/rust/candid_parser/tests/assets/ok/unicode.d.ts +++ b/rust/candid_parser/tests/assets/ok/unicode.d.ts @@ -1,6 +1,6 @@ -import type { Principal } from '@dfinity/principal'; -import type { ActorMethod } from '@dfinity/agent'; -import type { IDL } from '@dfinity/candid'; +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; export interface A { '\u{e000}' : bigint, From 4e9d3b8ad8e5227bd9d4afbb87ecd4ea73c8f418 Mon Sep 17 00:00:00 2001 From: Adam Spofford <93943719+adamspofford-dfinity@users.noreply.github.com> Date: Fri, 9 Jan 2026 09:55:56 -0800 Subject: [PATCH 05/43] feat: Add rangemap feature to ic_principal (#696) We use rangemap in ic-agent for canister ranges. It has a feature to implement the required trait even if the upstream type doesn't, but it blocks implementing Debug and a couple other annoyances so it'd be nice for ic_principal to implement it directly. --- Cargo.lock | 7 +++++++ rust/ic_principal/Cargo.toml | 2 ++ rust/ic_principal/src/lib.rs | 26 ++++++++++++++++++++++++++ rust/ic_principal/tests/principal.rs | 21 +++++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index c8842c8ba..ab5339114 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -694,6 +694,7 @@ dependencies = [ "crc32fast", "data-encoding", "impls", + "rangemap", "serde", "serde_cbor", "serde_json", @@ -1170,6 +1171,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + [[package]] name = "redox_syscall" version = "0.5.3" diff --git a/rust/ic_principal/Cargo.toml b/rust/ic_principal/Cargo.toml index 1e5f3ed48..3cddb67a5 100644 --- a/rust/ic_principal/Cargo.toml +++ b/rust/ic_principal/Cargo.toml @@ -18,6 +18,7 @@ include = ["src", "Cargo.toml", "LICENSE", "README.md"] arbitrary = { workspace = true, optional = true } crc32fast = { version = "1.3.0", optional = true } data-encoding = { version = "2.3.2", optional = true } +rangemap = { version = "1.6.0", optional = true } serde = { workspace = true, optional = true } sha2 = { version = "0.10.1", optional = true } thiserror = { version = "1.0", optional = true } @@ -35,6 +36,7 @@ arbitrary = ['dep:arbitrary', 'serde'] convert = ['dep:crc32fast', 'dep:data-encoding', 'dep:thiserror'] self_authenticating = ['dep:sha2'] serde = ['dep:serde', 'convert'] +rangemap = ['dep:rangemap'] [lints] workspace = true diff --git a/rust/ic_principal/src/lib.rs b/rust/ic_principal/src/lib.rs index b30465a57..7dfddfd5f 100644 --- a/rust/ic_principal/src/lib.rs +++ b/rust/ic_principal/src/lib.rs @@ -414,3 +414,29 @@ impl<'a> Arbitrary<'a> for Principal { Ok(principal) } } + +#[cfg(feature = "rangemap")] +impl rangemap::StepLite for Principal { + fn add_one(&self) -> Self { + let mut new = *self; + // do not increment the tag byte + for byte in new.bytes[..new.len as usize - 1].iter_mut().rev() { + *byte = byte.wrapping_add(1); + if *byte != 0 { + break; + } + } + new + } + fn sub_one(&self) -> Self { + let mut new = *self; + // do not decrement the tag byte + for byte in new.bytes[..new.len as usize - 1].iter_mut().rev() { + *byte = byte.wrapping_sub(1); + if *byte != 255 { + break; + } + } + new + } +} diff --git a/rust/ic_principal/tests/principal.rs b/rust/ic_principal/tests/principal.rs index 541e719e3..bf9b0438b 100644 --- a/rust/ic_principal/tests/principal.rs +++ b/rust/ic_principal/tests/principal.rs @@ -267,6 +267,9 @@ fn impl_traits() { #[cfg(feature = "serde")] assert!(impls::impls!(Principal: Serialize & Deserialize<'static>)); + + #[cfg(feature = "rangemap")] + assert!(impls::impls!(Principal: rangemap::StepLite)); } #[test] @@ -293,3 +296,21 @@ fn self_authenticating_ok() { ]); assert_eq!(p1, p2); } + +#[test] +#[cfg(feature = "rangemap")] +fn rangemap_steplite_impl_works() { + use rangemap::{RangeInclusiveSet, StepLite}; + + let p1 = Principal::from_slice(&[0, 1, 2, 0]); + let p2 = Principal::from_slice(&[0, 1, 3, 0]); + + assert_eq!(p1.add_one(), p2); + assert_eq!(p2.sub_one(), p1); + + let mut set = RangeInclusiveSet::new(); + set.insert(p1..=p2); + assert!(set.contains(&p1)); + assert!(set.contains(&p2)); + assert!(!set.contains(&Principal::from_slice(&[0, 1, 4, 0]))); +} From e643f7d7adfd43d685eaa6fedcec102b37643204 Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Fri, 9 Jan 2026 15:45:21 -0500 Subject: [PATCH 06/43] chore: release candid_parser v0.3.0, didc v0.6.0, ic_principal v0.1.2 (#697) --- CHANGELOG.md | 9 +++++++++ Cargo.lock | 28 ++++++++++++++-------------- rust/bench/Cargo.lock | 4 ++-- rust/candid_parser/Cargo.toml | 2 +- rust/ic_principal/Cargo.toml | 2 +- tools/didc/Cargo.toml | 2 +- tools/didc/README.md | 2 +- 7 files changed, 29 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cf1aeef2..6c546e627 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,18 @@ ## Unreleased +## 2026-01-09 + +### candid_parser 0.3.0 & didc 0.6.0 + * Breaking changes: + Changed imports generated by `candid_parser::bindings::typescript::compile` from `@dfinity/*` to `@icp-sdk/core/*` +### ic_principal 0.1.2 + +* Non-breaking changes: + + Implement `rangemap::StepLite` for `Principal` (requires the optional `rangemap` feature flag) + ## 2025-12-18 ### candid_parser 0.2.4 & didc 0.5.4 diff --git a/Cargo.lock b/Cargo.lock index ab5339114..898730482 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,7 @@ dependencies = [ "byteorder", "candid_derive 0.6.6", "hex", - "ic_principal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ic_principal 0.1.1", "leb128", "num-bigint", "num-traits", @@ -216,9 +216,9 @@ dependencies = [ "binread", "byteorder", "candid_derive 0.10.20", - "candid_parser 0.2.4", + "candid_parser 0.3.0", "hex", - "ic_principal 0.1.1", + "ic_principal 0.1.2", "leb128", "num-bigint", "num-traits", @@ -276,7 +276,7 @@ dependencies = [ [[package]] name = "candid_parser" -version = "0.2.4" +version = "0.3.0" dependencies = [ "anyhow", "arbitrary", @@ -479,10 +479,10 @@ dependencies = [ [[package]] name = "didc" -version = "0.5.4" +version = "0.6.0" dependencies = [ "anyhow", - "candid_parser 0.2.4", + "candid_parser 0.3.0", "clap", "console", "hex", @@ -689,30 +689,30 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "ic_principal" version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" dependencies = [ "arbitrary", "crc32fast", "data-encoding", - "impls", - "rangemap", "serde", - "serde_cbor", - "serde_json", - "serde_test", "sha2", "thiserror", ] [[package]] name = "ic_principal" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" +version = "0.1.2" dependencies = [ "arbitrary", "crc32fast", "data-encoding", + "impls", + "rangemap", "serde", + "serde_cbor", + "serde_json", + "serde_test", "sha2", "thiserror", ] diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index 0fed37da4..109b26483 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -178,7 +178,7 @@ dependencies = [ [[package]] name = "candid_parser" -version = "0.2.4" +version = "0.3.0" dependencies = [ "anyhow", "candid", @@ -498,7 +498,7 @@ checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" [[package]] name = "ic_principal" -version = "0.1.1" +version = "0.1.2" dependencies = [ "arbitrary", "crc32fast", diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml index cb221692e..3293440d1 100644 --- a/rust/candid_parser/Cargo.toml +++ b/rust/candid_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid_parser" -version = "0.2.4" +version = "0.3.0" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] diff --git a/rust/ic_principal/Cargo.toml b/rust/ic_principal/Cargo.toml index 3cddb67a5..5f889810e 100644 --- a/rust/ic_principal/Cargo.toml +++ b/rust/ic_principal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ic_principal" -version = "0.1.1" +version = "0.1.2" authors = ["DFINITY Stiftung "] edition = "2021" rust-version.workspace = true diff --git a/tools/didc/Cargo.toml b/tools/didc/Cargo.toml index 43fe2b382..52f4f9448 100644 --- a/tools/didc/Cargo.toml +++ b/tools/didc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "didc" -version = "0.5.4" +version = "0.6.0" authors = ["DFINITY Team"] edition = "2021" diff --git a/tools/didc/README.md b/tools/didc/README.md index 9c5d83e50..5fcb2dda9 100644 --- a/tools/didc/README.md +++ b/tools/didc/README.md @@ -3,7 +3,7 @@ A multi-purpose tool for Candid. ``` -didc 0.4.0 +didc 0.6.0 USAGE: didc From 281d685a43955c98396704045a1a6175c05924a5 Mon Sep 17 00:00:00 2001 From: venkkatesh-sekar <94061546+venkkatesh-sekar@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:46:56 +0100 Subject: [PATCH 07/43] chore: enforce limits in binary parsing (#698) Co-authored-by: Linwei Shang --- CHANGELOG.md | 7 +++++++ rust/candid/src/binary_parser.rs | 9 ++++++++- rust/candid/src/de.rs | 10 +++++++++- rust/candid/src/types/internal.rs | 1 + 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c546e627..def494fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +### Candid + +* Breaking changes: + + Enforce limits on type table size in the binary parser. +* Non-breaking changes: + + Add `max_type_len` to `DecoderConfig` to configure the type table size limit during binary parsing. + ## 2026-01-09 ### candid_parser 0.3.0 & didc 0.6.0 diff --git a/rust/candid/src/binary_parser.rs b/rust/candid/src/binary_parser.rs index d0c1ea0ea..5a0bd2e4b 100644 --- a/rust/candid/src/binary_parser.rs +++ b/rust/candid/src/binary_parser.rs @@ -5,6 +5,8 @@ use binread::io::{Read, Seek}; use binread::{BinRead, BinResult, Error as BError, ReadOptions}; use std::convert::TryInto; +const MAX_TYPE_TABLE_LEN: u64 = 10_000; // Max type entries + fn read_leb(reader: &mut R, ro: &ReadOptions, (): ()) -> BinResult { let pos = reader.stream_position()?; leb128::read::unsigned(reader).map_err(|_| BError::Custom { @@ -22,16 +24,21 @@ fn read_sleb(reader: &mut R, ro: &ReadOptions, (): ()) -> BinRes #[derive(BinRead, Debug)] #[br(magic = b"DIDL")] +#[br(import(max_type_len: Option))] pub struct Header { + #[br(args(max_type_len))] table: Table, #[br(parse_with = read_leb)] len: u64, #[br(count = len)] args: Vec, } + #[derive(BinRead, Debug)] +#[br(import(max_type_len: Option))] struct Table { - #[br(parse_with = read_leb, assert(len <= i64::MAX as u64, "type table size out of range"))] + #[br(parse_with = read_leb)] + #[br(assert(len <= max_type_len.unwrap_or(MAX_TYPE_TABLE_LEN as usize) as u64, "type table size exceeded"))] len: u64, #[br(count = len)] table: Vec, diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index ff4f20b72..2cb230720 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -143,6 +143,7 @@ impl<'de> IDLDeserialize<'de> { pub struct DecoderConfig { pub decoding_quota: Option, pub skipping_quota: Option, + pub max_type_len: Option, full_error_message: bool, } impl DecoderConfig { @@ -153,6 +154,7 @@ impl DecoderConfig { Self { decoding_quota: None, skipping_quota: None, + max_type_len: None, #[cfg(not(target_arch = "wasm32"))] full_error_message: true, #[cfg(target_arch = "wasm32")] @@ -212,6 +214,11 @@ impl DecoderConfig { self.skipping_quota = Some(n); self } + /// Set the max type table size + pub fn set_max_type_len(&mut self, n: usize) -> &mut Self { + self.max_type_len = Some(n); + self + } /// When set to false, error message only displays the concrete type when the type is small. /// The error message also doesn't include the decoding states. /// When set to true, error message always shows the full type and decoding states. @@ -230,6 +237,7 @@ impl DecoderConfig { Self { decoding_quota, skipping_quota, + max_type_len: original.max_type_len, full_error_message: original.full_error_message, } } @@ -318,7 +326,7 @@ struct Deserializer<'de> { impl<'de> Deserializer<'de> { fn from_bytes(bytes: &'de [u8], config: &DecoderConfig) -> Result { let mut reader = Cursor::new(bytes); - let header = Header::read(&mut reader)?; + let header = Header::read_args(&mut reader, (config.max_type_len,))?; let (env, types) = header.to_types()?; Ok(Deserializer { input: reader, diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 0bfd8cc05..b4a862ec4 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -16,6 +16,7 @@ pub struct TypeId { impl TypeId { pub fn of() -> Self { let name = std::any::type_name::(); + #[allow(function_casts_as_integer)] TypeId { id: TypeId::of:: as usize, name, From 022ec7e7f9b8061ed9b8342113638ad9b39e8c01 Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Tue, 3 Feb 2026 11:13:41 -0500 Subject: [PATCH 08/43] chore: Release candid v0.10.21 (#699) --- CHANGELOG.md | 8 +- Cargo.lock | 8 +- rust/bench/Cargo.lock | 266 ++++++++++++++++++---------------- rust/candid/Cargo.toml | 4 +- rust/candid_derive/Cargo.toml | 2 +- 5 files changed, 155 insertions(+), 133 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index def494fab..6b218e1a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,12 @@ ## Unreleased -### Candid +## 2026-02-03 + +### Candid 0.10.21 -* Breaking changes: - + Enforce limits on type table size in the binary parser. * Non-breaking changes: - + Add `max_type_len` to `DecoderConfig` to configure the type table size limit during binary parsing. + + Add `max_type_len` to `DecoderConfig` to configure the type table size limit (default: 10,000) during binary parsing. ## 2026-01-09 diff --git a/Cargo.lock b/Cargo.lock index 898730482..4b9144947 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,13 +209,13 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.20" +version = "0.10.21" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.20", + "candid_derive 0.10.21", "candid_parser 0.3.0", "hex", "ic_principal 0.1.2", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.20" +version = "0.10.21" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -280,7 +280,7 @@ version = "0.3.0" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.20", + "candid 0.10.21", "codespan-reporting", "console", "convert_case", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index 109b26483..b06f8fced 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -4,18 +4,27 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] [[package]] name = "arbitrary" @@ -103,9 +112,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -147,7 +156,7 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.20" +version = "0.10.21" dependencies = [ "anyhow", "binread", @@ -168,12 +177,12 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.20" +version = "0.10.21" dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -198,9 +207,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.37" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", @@ -208,9 +217,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "codespan-reporting" @@ -219,7 +228,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -257,9 +266,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -286,7 +295,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -297,14 +306,14 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "derive_builder" @@ -324,7 +333,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -334,7 +343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -391,9 +400,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -419,9 +428,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -430,9 +439,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.3.2" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" +checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e" dependencies = [ "derive_builder", "log", @@ -441,14 +450,14 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.16", + "thiserror 2.0.18", ] [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hex" @@ -487,7 +496,7 @@ dependencies = [ "quote", "serde", "serde_tokenstream", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -516,13 +525,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.11.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24855426e2ca0eb0c416e7e1f548b11f7547805faeed2a3d3e961286b51f7d53" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", - "serde", ] [[package]] @@ -536,9 +544,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "lalrpop" @@ -585,15 +593,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", @@ -601,19 +609,18 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos" @@ -636,7 +643,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -650,9 +657,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "new_debug_unreachable" @@ -704,11 +711,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -716,15 +732,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] @@ -735,20 +751,19 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pest" -version = "2.8.2" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", - "thiserror 2.0.16", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.2" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -756,22 +771,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.2" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "pest_meta" -version = "2.8.2" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2", @@ -810,47 +825,48 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac98773b7109bc75f475ab5a134c9b64b87e59d776d31098d8f346922396a477" +checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156" dependencies = [ "arrayvec", "typed-arena", - "unicode-width", + "unicode-width 0.2.2", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "psm" -version = "0.1.26" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +checksum = "1fa96cb91275ed31d6da3e983447320c4eb219ac180fa1679a0889ff32861e2d" dependencies = [ + "ar_archive_writer", "cc", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -868,9 +884,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -880,9 +896,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -891,9 +907,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustversion" @@ -901,12 +917,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" @@ -924,9 +934,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.224" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aaeb1e94f53b16384af593c71e20b095e958dab1d26939c1b70645c5cfbcc0b" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -944,35 +954,35 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.224" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f39390fa6346e24defbcdd3d9544ba8a19985d0af74df8501fbfe9a64341ab" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.224" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ff78ab5e8561c9a675bfc1785cb07ae721f0ee53329a595cefd8c04c2ac4e0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -993,7 +1003,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1015,9 +1025,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "smallvec" @@ -1027,9 +1037,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stacker" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" dependencies = [ "cc", "cfg-if", @@ -1069,9 +1079,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1109,11 +1119,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.18", ] [[package]] @@ -1124,18 +1134,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.114", ] [[package]] @@ -1189,9 +1199,9 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -1201,9 +1211,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -1217,6 +1227,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -1267,7 +1283,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -1278,9 +1294,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" @@ -1293,9 +1309,9 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] @@ -1366,9 +1382,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index e2a3aaa9b..b6dc68976 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.20" +version = "0.10.21" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.20" } +candid_derive = { path = "../candid_derive", version = "=0.10.21" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index f9dcc0f21..6e66b4c99 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.20" +version = "0.10.21" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] From 07d64145a65a6f98d731c5856a7d061a33c2bb37 Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Tue, 10 Feb 2026 15:05:18 -0500 Subject: [PATCH 09/43] feat: enhance recursion guard (#700) # Summary - Use `target_family = "wasm"` for platform detection to cover both wasm32 and wasm64; skip recursion check on wasm (sandboxed), use stack-based check on native platforms and a conservative depth limit on other niche platforms - Apply recursion guard to all recursive functions on deserialization path: type environment operations (`TypeEnv::is_empty`, `trace_type`, `rec_find_type`, `as_func`, `as_service`), value type annotation (`IDLValue::annotate_type`), and subtype checking (`subtype_()`, `equal()`) --------- Co-authored-by: Claude Opus 4.6 --- CHANGELOG.md | 6 ++ rust/candid/Cargo.toml | 2 +- rust/candid/src/de.rs | 91 ++++++++------------ rust/candid/src/types/subtype.rs | 137 +++++++++++++++++++++--------- rust/candid/src/types/type_env.rs | 64 +++++++++++--- rust/candid/src/types/value.rs | 50 +++++++---- rust/candid/src/utils.rs | 72 ++++++++++++++++ 7 files changed, 296 insertions(+), 126 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b218e1a4..27641a860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* Non-breaking changes: + + Enhance recursion guard + - Use `target_family = "wasm"` for platform detection to cover both wasm32 and wasm64; skip recursion check on wasm (sandboxed), use stack-based check on native platforms and a conservative depth limit on other niche platforms + - Apply recursion guard to all recursive functions on deserialization path: type environment operations (`TypeEnv::is_empty`, `trace_type`, `rec_find_type`, `as_func`, `as_service`), value type annotation (`IDLValue::annotate_type`), subtype checking (`subtype_()`, `equal()`), and all deserializer methods (`deserialize_option`, `deserialize_seq`, `deserialize_map`, `deserialize_tuple`, `deserialize_tuple_struct`, `deserialize_struct`, `deserialize_enum`) + - Refactor to use RAII guard pattern (`RecursionDepth` with `DepthGuard`) for automatic depth management, eliminating manual increment/decrement operations + ## 2026-02-03 ### Candid 0.10.21 diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index b6dc68976..63987877d 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -32,7 +32,7 @@ pretty = { workspace = true, optional = true } num-bigint = { workspace = true, optional = true } num-traits = { workspace = true, optional = true } -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +[target.'cfg(not(target_family = "wasm"))'.dependencies] stacker = "0.1" [dev-dependencies] diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index 2cb230720..a613bd553 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -67,7 +67,10 @@ impl<'de> IDLDeserialize<'de> { where T: de::Deserialize<'de> + CandidType, { - let expected_type = self.de.table.trace_type(&expected_type)?; + let expected_type = self + .de + .table + .trace_type_with_depth(&expected_type, &self.de.recursion_depth)?; if self.de.types.is_empty() { if matches!( expected_type.as_ref(), @@ -281,27 +284,6 @@ macro_rules! check { } }}; } -#[cfg(not(target_arch = "wasm32"))] -macro_rules! check_recursion { - ($this:ident $($body:tt)*) => { - $this.recursion_depth += 1; - match stacker::remaining_stack() { - Some(size) if size < 32768 => return Err(Error::msg(format!("Recursion limit exceeded at depth {}", $this.recursion_depth))), - None if $this.recursion_depth > 512 => return Err(Error::msg(format!("Recursion limit exceeded at depth {}. Cannot detect stack size, use a conservative bound", $this.recursion_depth))), - _ => (), - } - let __ret = { $this $($body)* }; - $this.recursion_depth -= 1; - __ret - }; -} -// No need to check recursion depth for wasm32, because canisters are running in a sandbox -#[cfg(target_arch = "wasm32")] -macro_rules! check_recursion { - ($this:ident $($body:tt)*) => { - $this $($body)* - }; -} #[derive(Clone)] struct Deserializer<'de> { @@ -319,8 +301,7 @@ struct Deserializer<'de> { // It only affects the field id generation in enum type. is_untyped: bool, config: DecoderConfig, - #[cfg(not(target_arch = "wasm32"))] - recursion_depth: u16, + recursion_depth: crate::utils::RecursionDepth, } impl<'de> Deserializer<'de> { @@ -338,8 +319,7 @@ impl<'de> Deserializer<'de> { field_name: None, is_untyped: false, config: config.clone(), - #[cfg(not(target_arch = "wasm32"))] - recursion_depth: 0, + recursion_depth: crate::utils::RecursionDepth::new(), }) } fn dump_state(&self) -> String { @@ -403,14 +383,18 @@ impl<'de> Deserializer<'de> { TypeInner::Var(_) | TypeInner::Knot(_) ) { self.add_cost(1)?; - self.expect_type = self.table.trace_type(&self.expect_type)?; + self.expect_type = self + .table + .trace_type_with_depth(&self.expect_type, &self.recursion_depth)?; } if matches!( self.wire_type.as_ref(), TypeInner::Var(_) | TypeInner::Knot(_) ) { self.add_cost(1)?; - self.wire_type = self.table.trace_type(&self.wire_type)?; + self.wire_type = self + .table + .trace_type_with_depth(&self.wire_type, &self.recursion_depth)?; } Ok(()) } @@ -837,18 +821,18 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { self.wire_type = t1.clone(); self.expect_type = t2.clone(); if BoolValue::read(&mut self.input)?.0 { - check_recursion! { - self.recoverable_visit_some(visitor) - } + let _guard = self.recursion_depth.guard()?; + self.recoverable_visit_some(visitor) } else { visitor.visit_none() } } (_, TypeInner::Opt(t2)) => { - self.expect_type = self.table.trace_type(t2)?; - check_recursion! { - self.recoverable_visit_some(visitor) - } + self.expect_type = self + .table + .trace_type_with_depth(t2, &self.recursion_depth)?; + let _guard = self.recursion_depth.guard()?; + self.recoverable_visit_some(visitor) } (_, _) => check!(false), } @@ -857,13 +841,13 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { + let _guard = self.recursion_depth.guard()?; self.unroll_type()?; self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { (TypeInner::Vec(e), TypeInner::Vec(w)) => { let expect = e.clone(); - let wire = self.table.trace_type(w)?; + let wire = self.table.trace_type_with_depth(w, &self.recursion_depth)?; let len = Len::read(&mut self.input)?.0; visitor.visit_seq(Compound::new(self, Style::Vector { len, expect, wire })) } @@ -883,7 +867,6 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { } _ => check!(false), } - } } fn deserialize_byte_buf>(self, visitor: V) -> Result { self.unroll_type()?; @@ -914,13 +897,13 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { + let _guard = self.recursion_depth.guard()?; self.unroll_type()?; self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { (TypeInner::Vec(e), TypeInner::Vec(w)) => { - let e = self.table.trace_type(e)?; - let w = self.table.trace_type(w)?; + let e = self.table.trace_type_with_depth(e, &self.recursion_depth)?; + let w = self.table.trace_type_with_depth(w, &self.recursion_depth)?; match (e.as_ref(), w.as_ref()) { (TypeInner::Record(ref e), TypeInner::Record(ref w)) => { match (&e[..], &w[..]) { @@ -948,16 +931,14 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { } _ => check!(false), } - } } fn deserialize_tuple(self, _len: usize, visitor: V) -> Result where V: Visitor<'de>, { - check_recursion! { - self.add_cost(1)?; - self.deserialize_seq(visitor) - } + let _guard = self.recursion_depth.guard()?; + self.add_cost(1)?; + self.deserialize_seq(visitor) } fn deserialize_tuple_struct( self, @@ -968,10 +949,9 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { - self.add_cost(1)?; - self.deserialize_seq(visitor) - } + let _guard = self.recursion_depth.guard()?; + self.add_cost(1)?; + self.deserialize_seq(visitor) } fn deserialize_struct( self, @@ -982,7 +962,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { + let _guard = self.recursion_depth.guard()?; self.unroll_type()?; self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { @@ -995,7 +975,6 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { } _ => check!(false), } - } } fn deserialize_enum( self, @@ -1006,7 +985,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - check_recursion! { + let _guard = self.recursion_depth.guard()?; self.unroll_type()?; self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { @@ -1029,7 +1008,6 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { } _ => check!(false), } - } } fn deserialize_identifier(self, visitor: V) -> Result where @@ -1166,7 +1144,10 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { let field = e.id.clone(); self.de.set_field_name(field.clone()); let expect = expect.pop_front().unwrap().ty; - self.de.expect_type = self.de.table.trace_type(&expect)?; + self.de.expect_type = self + .de + .table + .trace_type_with_depth(&expect, &self.de.recursion_depth)?; check!( matches!( self.de.expect_type.as_ref(), diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index 5c4d9fdd3..df67df9f7 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -1,5 +1,6 @@ use super::internal::{find_type, Field, Label, Type, TypeInner}; use crate::types::TypeEnv; +use crate::utils::RecursionDepth; use crate::{Error, Result}; use anyhow::Context; use std::collections::{HashMap, HashSet}; @@ -15,7 +16,14 @@ pub enum OptReport { } /// Check if t1 <: t2 pub fn subtype(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<()> { - subtype_(OptReport::Warning, gamma, env, t1, t2) + subtype_( + OptReport::Warning, + gamma, + env, + t1, + t2, + &RecursionDepth::new(), + ) } /// Check if t1 <: t2, and report the special opt rule as `Slience`, `Warning` or `Error`. pub fn subtype_with_config( @@ -25,7 +33,7 @@ pub fn subtype_with_config( t1: &Type, t2: &Type, ) -> Result<()> { - subtype_(report, gamma, env, t1, t2) + subtype_(report, gamma, env, t1, t2, &RecursionDepth::new()) } fn subtype_( @@ -34,7 +42,9 @@ fn subtype_( env: &TypeEnv, t1: &Type, t2: &Type, + depth: &RecursionDepth, ) -> Result<()> { + let _guard = depth.guard()?; use TypeInner::*; if t1 == t2 { return Ok(()); @@ -44,10 +54,24 @@ fn subtype_( return Ok(()); } let res = match (t1.as_ref(), t2.as_ref()) { - (Var(id), _) => subtype_(report, gamma, env, env.rec_find_type(id).unwrap(), t2), - (_, Var(id)) => subtype_(report, gamma, env, t1, env.rec_find_type(id).unwrap()), - (Knot(id), _) => subtype_(report, gamma, env, &find_type(id).unwrap(), t2), - (_, Knot(id)) => subtype_(report, gamma, env, t1, &find_type(id).unwrap()), + (Var(id), _) => subtype_( + report, + gamma, + env, + env.rec_find_type_with_depth(id, depth).unwrap(), + t2, + depth, + ), + (_, Var(id)) => subtype_( + report, + gamma, + env, + t1, + env.rec_find_type_with_depth(id, depth).unwrap(), + depth, + ), + (Knot(id), _) => subtype_(report, gamma, env, &find_type(id).unwrap(), t2, depth), + (_, Knot(id)) => subtype_(report, gamma, env, t1, &find_type(id).unwrap(), depth), (_, _) => unreachable!(), }; if res.is_err() { @@ -59,12 +83,15 @@ fn subtype_( (_, Reserved) => Ok(()), (Empty, _) => Ok(()), (Nat, Int) => Ok(()), - (Vec(ty1), Vec(ty2)) => subtype_(report, gamma, env, ty1, ty2), + (Vec(ty1), Vec(ty2)) => subtype_(report, gamma, env, ty1, ty2, depth), (Null, Opt(_)) => Ok(()), - (Opt(ty1), Opt(ty2)) if subtype_(report, gamma, env, ty1, ty2).is_ok() => Ok(()), + (Opt(ty1), Opt(ty2)) if subtype_(report, gamma, env, ty1, ty2, depth).is_ok() => Ok(()), (_, Opt(ty2)) - if subtype_(report, gamma, env, t1, ty2).is_ok() - && !matches!(env.trace_type(ty2)?.as_ref(), Null | Reserved | Opt(_)) => + if subtype_(report, gamma, env, t1, ty2, depth).is_ok() + && !matches!( + env.trace_type_with_depth(ty2, depth)?.as_ref(), + Null | Reserved | Opt(_) + ) => { Ok(()) } @@ -81,11 +108,16 @@ fn subtype_( let fields: HashMap<_, _> = fs1.iter().map(|Field { id, ty }| (id, ty)).collect(); for Field { id, ty: ty2 } in fs2 { match fields.get(id) { - Some(ty1) => subtype_(report, gamma, env, ty1, ty2).with_context(|| { - format!("Record field {id}: {ty1} is not a subtype of {ty2}") - })?, + Some(ty1) => { + subtype_(report, gamma, env, ty1, ty2, depth).with_context(|| { + format!("Record field {id}: {ty1} is not a subtype of {ty2}") + })? + } None => { - if !matches!(env.trace_type(ty2)?.as_ref(), Null | Reserved | Opt(_)) { + if !matches!( + env.trace_type_with_depth(ty2, depth)?.as_ref(), + Null | Reserved | Opt(_) + ) { return Err(Error::msg(format!("Record field {id}: {ty2} is only in the expected type and is not of type opt, null or reserved"))); } } @@ -97,13 +129,15 @@ fn subtype_( let fields: HashMap<_, _> = fs2.iter().map(|Field { id, ty }| (id, ty)).collect(); for Field { id, ty: ty1 } in fs1 { match fields.get(id) { - Some(ty2) => subtype_(report, gamma, env, ty1, ty2).with_context(|| { - format!("Variant field {id}: {ty1} is not a subtype_ of {ty2}") - })?, + Some(ty2) => { + subtype_(report, gamma, env, ty1, ty2, depth).with_context(|| { + format!("Variant field {id}: {ty1} is not a subtype of {ty2}") + })? + } None => { return Err(Error::msg(format!( "Variant field {id} not found in the expected type" - ))) + ))); } } } @@ -113,13 +147,15 @@ fn subtype_( let meths: HashMap<_, _> = ms1.iter().cloned().collect(); for (name, ty2) in ms2 { match meths.get(name) { - Some(ty1) => subtype_(report, gamma, env, ty1, ty2).with_context(|| { - format!("Method {name}: {ty1} is not a subtype of {ty2}") - })?, + Some(ty1) => { + subtype_(report, gamma, env, ty1, ty2, depth).with_context(|| { + format!("Method {name}: {ty1} is not a subtype of {ty2}") + })? + } None => { return Err(Error::msg(format!( "Method {name} is only in the expected type" - ))) + ))); } } } @@ -133,15 +169,15 @@ fn subtype_( let args2 = to_tuple(&f2.args); let rets1 = to_tuple(&f1.rets); let rets2 = to_tuple(&f2.rets); - subtype_(report, gamma, env, &args2, &args1) + subtype_(report, gamma, env, &args2, &args1, depth) .context("Subtype fails at function input type")?; - subtype_(report, gamma, env, &rets1, &rets2) + subtype_(report, gamma, env, &rets1, &rets2, depth) .context("Subtype fails at function return type")?; Ok(()) } // This only works in the first order case, but service constructor only appears at the top level according to the spec. - (Class(_, t), _) => subtype_(report, gamma, env, t, t2), - (_, Class(_, t)) => subtype_(report, gamma, env, t1, t), + (Class(_, t), _) => subtype_(report, gamma, env, t, t2, depth), + (_, Class(_, t)) => subtype_(report, gamma, env, t1, t, depth), (Unknown, _) => unreachable!(), (_, Unknown) => unreachable!(), (_, _) => Err(Error::msg(format!("{t1} is not a subtype of {t2}"))), @@ -151,6 +187,17 @@ fn subtype_( /// Check if t1 and t2 are structurally equivalent, ignoring the variable naming differences. /// Note that this is more strict than `t1 <: t2` and `t2 <: t1`, because of the special opt rule. pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<()> { + equal_impl(gamma, env, t1, t2, &RecursionDepth::new()) +} + +fn equal_impl( + gamma: &mut Gamma, + env: &TypeEnv, + t1: &Type, + t2: &Type, + depth: &RecursionDepth, +) -> Result<()> { + let _guard = depth.guard()?; use TypeInner::*; if t1 == t2 { return Ok(()); @@ -160,10 +207,22 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( return Ok(()); } let res = match (t1.as_ref(), t2.as_ref()) { - (Var(id), _) => equal(gamma, env, env.rec_find_type(id).unwrap(), t2), - (_, Var(id)) => equal(gamma, env, t1, env.rec_find_type(id).unwrap()), - (Knot(id), _) => equal(gamma, env, &find_type(id).unwrap(), t2), - (_, Knot(id)) => equal(gamma, env, t1, &find_type(id).unwrap()), + (Var(id), _) => equal_impl( + gamma, + env, + env.rec_find_type_with_depth(id, depth).unwrap(), + t2, + depth, + ), + (_, Var(id)) => equal_impl( + gamma, + env, + t1, + env.rec_find_type_with_depth(id, depth).unwrap(), + depth, + ), + (Knot(id), _) => equal_impl(gamma, env, &find_type(id).unwrap(), t2, depth), + (_, Knot(id)) => equal_impl(gamma, env, t1, &find_type(id).unwrap(), depth), (_, _) => unreachable!(), }; if res.is_err() { @@ -172,8 +231,8 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( return res; } match (t1.as_ref(), t2.as_ref()) { - (Opt(ty1), Opt(ty2)) => equal(gamma, env, ty1, ty2), - (Vec(ty1), Vec(ty2)) => equal(gamma, env, ty1, ty2), + (Opt(ty1), Opt(ty2)) => equal_impl(gamma, env, ty1, ty2, depth), + (Vec(ty1), Vec(ty2)) => equal_impl(gamma, env, ty1, ty2, depth), (Record(fs1), Record(fs2)) | (Variant(fs1), Variant(fs2)) => { assert_length(fs1, fs2, |x| x.id.clone(), |x| x.to_string()) .context("Different field length")?; @@ -184,7 +243,7 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( f1.id, f2.id ))); } - equal(gamma, env, &f1.ty, &f2.ty).context(format!( + equal_impl(gamma, env, &f1.ty, &f2.ty, depth).context(format!( "Field {} has different types: {} and {}", f1.id, f1.ty, f2.ty ))?; @@ -201,7 +260,7 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( m1.0, m2.0 ))); } - equal(gamma, env, &m1.1, &m2.1).context(format!( + equal_impl(gamma, env, &m1.1, &m2.1, depth).context(format!( "Method {} has different types: {} and {}", m1.0, m1.1, m2.1 ))?; @@ -216,19 +275,21 @@ pub fn equal(gamma: &mut Gamma, env: &TypeEnv, t1: &Type, t2: &Type) -> Result<( let args2 = to_tuple(&f2.args); let rets1 = to_tuple(&f1.rets); let rets2 = to_tuple(&f2.rets); - equal(gamma, env, &args1, &args2).context("Mismatch in function input type")?; - equal(gamma, env, &rets1, &rets2).context("Mismatch in function return type")?; + equal_impl(gamma, env, &args1, &args2, depth) + .context("Mismatch in function input type")?; + equal_impl(gamma, env, &rets1, &rets2, depth) + .context("Mismatch in function return type")?; Ok(()) } (Class(init1, ty1), Class(init2, ty2)) => { let init_1 = to_tuple(init1); let init_2 = to_tuple(init2); - equal(gamma, env, &init_1, &init_2).context(format!( + equal_impl(gamma, env, &init_1, &init_2, depth).context(format!( "Mismatch in init args: {} and {}", pp_args(init1), pp_args(init2) ))?; - equal(gamma, env, ty1, ty2) + equal_impl(gamma, env, ty1, ty2, depth) } (Unknown, _) => unreachable!(), (_, Unknown) => unreachable!(), diff --git a/rust/candid/src/types/type_env.rs b/rust/candid/src/types/type_env.rs index 0723f2769..d66e209fd 100644 --- a/rust/candid/src/types/type_env.rs +++ b/rust/candid/src/types/type_env.rs @@ -1,4 +1,5 @@ use crate::types::{Function, Type, TypeInner}; +use crate::utils::RecursionDepth; use crate::{Error, Result}; use std::collections::BTreeMap; @@ -42,33 +43,65 @@ impl TypeEnv { } } pub fn rec_find_type(&self, name: &str) -> Result<&Type> { + self.rec_find_type_with_depth(name, &RecursionDepth::new()) + } + pub(crate) fn rec_find_type_with_depth( + &self, + name: &str, + depth: &RecursionDepth, + ) -> Result<&Type> { + let _guard = depth.guard()?; let t = self.find_type(name)?; match t.as_ref() { - TypeInner::Var(id) => self.rec_find_type(id), + TypeInner::Var(id) => self.rec_find_type_with_depth(id, depth), _ => Ok(t), } } pub fn trace_type<'a>(&'a self, t: &'a Type) -> Result { + self.trace_type_with_depth(t, &RecursionDepth::new()) + } + pub(crate) fn trace_type_with_depth<'a>( + &'a self, + t: &'a Type, + depth: &RecursionDepth, + ) -> Result { + let _guard = depth.guard()?; match t.as_ref() { - TypeInner::Var(id) => self.trace_type(self.find_type(id)?), + TypeInner::Var(id) => self.trace_type_with_depth(self.find_type(id)?, depth), TypeInner::Knot(ref id) => { - self.trace_type(&crate::types::internal::find_type(id).unwrap()) + self.trace_type_with_depth(&crate::types::internal::find_type(id).unwrap(), depth) } _ => Ok(t.clone()), } } pub fn as_func<'a>(&'a self, t: &'a Type) -> Result<&'a Function> { + self.as_func_with_depth(t, &RecursionDepth::new()) + } + fn as_func_with_depth<'a>( + &'a self, + t: &'a Type, + depth: &RecursionDepth, + ) -> Result<&'a Function> { + let _guard = depth.guard()?; match t.as_ref() { TypeInner::Func(f) => Ok(f), - TypeInner::Var(id) => self.as_func(self.find_type(id)?), + TypeInner::Var(id) => self.as_func_with_depth(self.find_type(id)?, depth), _ => Err(Error::msg(format!("not a function type: {t}"))), } } pub fn as_service<'a>(&'a self, t: &'a Type) -> Result<&'a [(String, Type)]> { + self.as_service_with_depth(t, &RecursionDepth::new()) + } + fn as_service_with_depth<'a>( + &'a self, + t: &'a Type, + depth: &RecursionDepth, + ) -> Result<&'a [(String, Type)]> { + let _guard = depth.guard()?; match t.as_ref() { TypeInner::Service(s) => Ok(s), - TypeInner::Var(id) => self.as_service(self.find_type(id)?), - TypeInner::Class(_, ty) => self.as_service(ty), + TypeInner::Var(id) => self.as_service_with_depth(self.find_type(id)?, depth), + TypeInner::Class(_, ty) => self.as_service_with_depth(ty, depth), _ => Err(Error::msg(format!("not a service type: {t}"))), } } @@ -84,8 +117,10 @@ impl TypeEnv { &'a self, res: &mut BTreeMap<&'a str, Option>, id: &'a str, + depth: &RecursionDepth, ) -> Result { - match res.get(id) { + let _guard = depth.guard()?; + let result = match res.get(id) { None => { res.insert(id, None); let t = self.find_type(id)?; @@ -94,7 +129,7 @@ impl TypeEnv { for f in fs { // Assume env only comes from type table, f.ty is either primitive or var. if let TypeInner::Var(f_id) = f.ty.as_ref() { - if self.is_empty(res, f_id)? { + if self.is_empty(res, f_id, depth)? { res.insert(id, Some(true)); return Ok(true); } @@ -102,23 +137,24 @@ impl TypeEnv { } false } - TypeInner::Var(id) => self.is_empty(res, id)?, + TypeInner::Var(id) => self.is_empty(res, id, depth)?, _ => false, }; res.insert(id, Some(result)); - Ok(result) + result } Some(None) => { res.insert(id, Some(true)); - Ok(true) + true } - Some(Some(b)) => Ok(*b), - } + Some(Some(b)) => *b, + }; + Ok(result) } pub fn replace_empty(&mut self) -> Result<()> { let mut res = BTreeMap::new(); for name in self.0.keys() { - self.is_empty(&mut res, name)?; + self.is_empty(&mut res, name, &RecursionDepth::new())?; } let ids: Vec<_> = res .iter() diff --git a/rust/candid/src/types/value.rs b/rust/candid/src/types/value.rs index 4720d6569..efdb87186 100644 --- a/rust/candid/src/types/value.rs +++ b/rust/candid/src/types/value.rs @@ -160,14 +160,24 @@ impl IDLValue { /// string, we need to set `from_parser` to true to enable converting numbers to the expected /// types, and disable the opt rules. pub fn annotate_type(&self, from_parser: bool, env: &TypeEnv, t: &Type) -> Result { + self.annotate_type_with_depth(from_parser, env, t, &crate::utils::RecursionDepth::new()) + } + fn annotate_type_with_depth( + &self, + from_parser: bool, + env: &TypeEnv, + t: &Type, + depth: &crate::utils::RecursionDepth, + ) -> Result { + let _guard = depth.guard()?; Ok(match (self, t.as_ref()) { (_, TypeInner::Var(id)) => { - let ty = env.rec_find_type(id)?; - self.annotate_type(from_parser, env, ty)? + let ty = env.rec_find_type_with_depth(id, depth)?; + self.annotate_type_with_depth(from_parser, env, ty, depth)? } (_, TypeInner::Knot(ref id)) => { let ty = crate::types::internal::find_type(id).unwrap(); - self.annotate_type(from_parser, env, &ty)? + self.annotate_type_with_depth(from_parser, env, &ty, depth)? } (_, TypeInner::Reserved) => IDLValue::Reserved, (IDLValue::Float64(n), TypeInner::Float32) if from_parser => { @@ -193,23 +203,23 @@ impl IDLValue { (IDLValue::Null, TypeInner::Opt(_)) => IDLValue::None, (IDLValue::Reserved, TypeInner::Opt(_)) => IDLValue::None, (IDLValue::None, TypeInner::Opt(_)) => IDLValue::None, - (IDLValue::Opt(v), TypeInner::Opt(ty)) if from_parser => { - IDLValue::Opt(Box::new(v.annotate_type(from_parser, env, ty)?)) - } + (IDLValue::Opt(v), TypeInner::Opt(ty)) if from_parser => IDLValue::Opt(Box::new( + v.annotate_type_with_depth(from_parser, env, ty, depth)?, + )), // liberal decoding of optionals (IDLValue::Opt(v), TypeInner::Opt(ty)) if !from_parser => v - .annotate_type(from_parser, env, ty) + .annotate_type_with_depth(from_parser, env, ty, depth) .map(|v| IDLValue::Opt(Box::new(v))) .unwrap_or(IDLValue::None), // try consituent type (v, TypeInner::Opt(ty)) if !from_parser && !matches!( - env.trace_type(ty)?.as_ref(), + env.trace_type_with_depth(ty, depth)?.as_ref(), TypeInner::Null | TypeInner::Reserved | TypeInner::Opt(_) ) => { - v.annotate_type(from_parser, env, ty) + v.annotate_type_with_depth(from_parser, env, ty, depth) .map(|v| IDLValue::Opt(Box::new(v))) .unwrap_or(IDLValue::None) } @@ -234,7 +244,7 @@ impl IDLValue { (IDLValue::Vec(vec), TypeInner::Vec(ty)) => { let mut res = Vec::with_capacity(vec.len()); for e in vec.iter() { - let v = e.annotate_type(from_parser, env, ty)?; + let v = e.annotate_type_with_depth(from_parser, env, ty, depth)?; res.push(v); } IDLValue::Vec(res) @@ -247,14 +257,16 @@ impl IDLValue { let val = fields .get(id.as_ref()) .cloned() - .or_else(|| match env.trace_type(ty).unwrap().as_ref() { - TypeInner::Null => Some(&IDLValue::Null), - TypeInner::Opt(_) => Some(&IDLValue::None), - TypeInner::Reserved => Some(&IDLValue::Reserved), - _ => None, - }) + .or_else( + || match env.trace_type_with_depth(ty, depth).unwrap().as_ref() { + TypeInner::Null => Some(&IDLValue::Null), + TypeInner::Opt(_) => Some(&IDLValue::None), + TypeInner::Reserved => Some(&IDLValue::Reserved), + _ => None, + }, + ) .ok_or_else(|| Error::msg(format!("record field {id} not found")))?; - let val = val.annotate_type(from_parser, env, ty)?; + let val = val.annotate_type_with_depth(from_parser, env, ty, depth)?; res.push(IDLField { id: id.as_ref().clone(), val, @@ -265,7 +277,9 @@ impl IDLValue { (IDLValue::Variant(v), TypeInner::Variant(fs)) => { for (i, f) in fs.iter().enumerate() { if v.0.id == *f.id { - let val = v.0.val.annotate_type(from_parser, env, &f.ty)?; + let val = + v.0.val + .annotate_type_with_depth(from_parser, env, &f.ty, depth)?; let field = IDLField { id: f.id.as_ref().clone(), val, diff --git a/rust/candid/src/utils.rs b/rust/candid/src/utils.rs index 39061244c..0a63de5ab 100644 --- a/rust/candid/src/utils.rs +++ b/rust/candid/src/utils.rs @@ -3,6 +3,78 @@ use crate::ser::IDLBuilder; use crate::{CandidType, Error, Result}; use serde::de::Deserialize; +/// Guard against stack overflow from deep recursion. +/// +/// On native platforms, uses `stacker::remaining_stack()` to check the actual +/// remaining stack space (< 32 KiB triggers an error). If the stack size cannot +/// be determined (exotic platforms), falls back to a conservative depth limit of 512. +/// +/// On wasm targets, this is a no-op. Wasm execution is always sandboxed (both in +/// canisters and in browsers), so a stack overflow cannot compromise the host. +/// Imposing a fixed depth limit would risk rejecting legitimate deeply-nested +/// payloads, since `stacker` cannot measure the actual remaining stack on wasm. +fn check_recursion_depth(_depth: u16) -> Result<()> { + #[cfg(not(target_family = "wasm"))] + match stacker::remaining_stack() { + Some(size) if size < 32768 => { + return Err(Error::msg(format!( + "Recursion limit exceeded at depth {_depth}" + ))) + } + None if _depth > 512 => { + return Err(Error::msg(format!( + "Recursion limit exceeded at depth {_depth}. Cannot detect stack size, use a conservative bound" + ))) + } + _ => (), + } + Ok(()) +} + +/// Tracks recursion depth with overflow protection. +/// +/// Use `guard()` to increment depth with automatic decrement via RAII. +/// Uses interior mutability to allow passing to recursive functions and cloning. +#[derive(Debug, Clone)] +pub(crate) struct RecursionDepth(std::rc::Rc>); + +impl RecursionDepth { + /// Creates a new recursion depth tracker starting at 0. + pub(crate) fn new() -> Self { + Self(std::rc::Rc::new(std::cell::Cell::new(0))) + } + + /// Increments depth and returns a guard that will automatically decrement on drop. + /// + /// Returns an error if recursion limit is exceeded. + pub(crate) fn guard(&self) -> Result { + let current = self.0.get(); + let new_depth = current + .checked_add(1) + .ok_or_else(|| Error::msg("Recursion depth overflow"))?; + self.0.set(new_depth); + let guard = DepthGuard(self.0.clone()); + check_recursion_depth(new_depth)?; + Ok(guard) + } +} + +/// RAII guard that automatically decrements recursion depth on drop. +/// +/// Created by calling `RecursionDepth::guard()`. +#[derive(Debug)] +pub(crate) struct DepthGuard(std::rc::Rc>); + +impl Drop for DepthGuard { + fn drop(&mut self) { + let current = self.0.get(); + let new_depth = current + .checked_sub(1) + .expect("Recursion depth underflow - this is a bug"); + self.0.set(new_depth); + } +} + pub fn check_unique<'a, I, T>(sorted: I) -> Result<()> where T: 'a + PartialEq + std::fmt::Display, From cb5e687e9b93ca1f78494311be1111e79c56cd81 Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Tue, 10 Feb 2026 15:14:45 -0500 Subject: [PATCH 10/43] chore: Release candid v0.10.22 (#701) --- CHANGELOG.md | 4 ++++ Cargo.lock | 8 ++++---- rust/bench/Cargo.lock | 4 ++-- rust/candid/Cargo.toml | 4 ++-- rust/candid_derive/Cargo.toml | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27641a860..ba79edf54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 2026-02-10 + +### Candid 0.10.22 + * Non-breaking changes: + Enhance recursion guard - Use `target_family = "wasm"` for platform detection to cover both wasm32 and wasm64; skip recursion check on wasm (sandboxed), use stack-based check on native platforms and a conservative depth limit on other niche platforms diff --git a/Cargo.lock b/Cargo.lock index 4b9144947..709b177ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,13 +209,13 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.21" +version = "0.10.22" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.21", + "candid_derive 0.10.22", "candid_parser 0.3.0", "hex", "ic_principal 0.1.2", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.21" +version = "0.10.22" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -280,7 +280,7 @@ version = "0.3.0" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.21", + "candid 0.10.22", "codespan-reporting", "console", "convert_case", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index b06f8fced..089bbfa8b 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.21" +version = "0.10.22" dependencies = [ "anyhow", "binread", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.21" +version = "0.10.22" dependencies = [ "lazy_static", "proc-macro2", diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index 63987877d..143e62f9e 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.21" +version = "0.10.22" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.21" } +candid_derive = { path = "../candid_derive", version = "=0.10.22" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index 6e66b4c99..b13e65f9b 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.21" +version = "0.10.22" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] From fafac8111ecf3696b7fc1920c66940d0d3015a13 Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Thu, 19 Feb 2026 09:54:42 -0800 Subject: [PATCH 11/43] fix: avoid double_parens lint in Decode! macro for single value (#703) Unblocks #702 --------- Co-authored-by: Claude Sonnet 4.6 --- rust/candid/src/utils.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rust/candid/src/utils.rs b/rust/candid/src/utils.rs index 0a63de5ab..1f5638a85 100644 --- a/rust/candid/src/utils.rs +++ b/rust/candid/src/utils.rs @@ -162,6 +162,9 @@ macro_rules! Decode { $de.get_value::<$ty>() .and_then(|val| Decode!(@GetValue [$($ans)* val] $de $($tail,)* )) }}; + (@GetValue [$ans:ident] $de:ident) => {{ + Ok($ans) + }}; (@GetValue [$($ans:ident)*] $de:ident) => {{ Ok(($($ans),*)) }}; @@ -598,3 +601,18 @@ encode_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l encode_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O); #[rustfmt::skip] encode_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L, m: M, n: N, o: O, p: P); + +#[cfg(test)] +mod tests { + + /// Ensures that the code does not trigger the `clippy::double_parens` lint. + /// This test was added to prevent regressions where unnecessary parentheses + /// could cause Clippy warnings or errors. + #[test] + fn test_decode_single_value() { + let bytes = Encode!(&"hello").unwrap(); + // This code used to get `clippy::double_parens` error. + let value = Decode!(&bytes, String).unwrap(); + assert_eq!(value, "hello"); + } +} From a267d71d58532f6a5b44c62cb068bb9434be1316 Mon Sep 17 00:00:00 2001 From: michael-weigelt <122277901+michael-weigelt@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:37:44 +0100 Subject: [PATCH 12/43] feat: Add BoundedVec (#702) This makes the `BoundedVec` type, thus far used in the [management canister types](https://sourcegraph.com/github.com/dfinity/ic/-/blob/rs/types/management_canister_types/src/bounded_vec.rs?L14), available for public use. --------- Co-authored-by: Linwei Shang --- CHANGELOG.md | 5 + rust/candid/src/types/bounded_vec.rs | 471 +++++++++++++++++++++++++++ rust/candid/src/types/mod.rs | 1 + 3 files changed, 477 insertions(+) create mode 100644 rust/candid/src/types/bounded_vec.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ba79edf54..24223d5ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Candid + +* Non-breaking changes: + + Add `BoundedVec` type to `candid::types::bounded_vec` for bounding a vector by number of elements, total data size, and per-element data size during deserialization + ## 2026-02-10 ### Candid 0.10.22 diff --git a/rust/candid/src/types/bounded_vec.rs b/rust/candid/src/types/bounded_vec.rs new file mode 100644 index 000000000..c5ca0d4ae --- /dev/null +++ b/rust/candid/src/types/bounded_vec.rs @@ -0,0 +1,471 @@ +use data_size::DataSize; +use serde::{Deserialize, Deserializer}; +use std::fmt; + +use crate::{types::TypeInner, CandidType}; + +/// Indicates that `BoundedVec<...>` template parameter (eg. length, total data size, etc) is unbounded. +pub const UNBOUNDED: usize = usize::MAX; + +/// Struct for bounding a vector by different parameters: +/// - number of elements +/// - total data size in bytes +/// - single element data size in bytes +/// +/// ``` +/// # use candid::{Decode, Encode}; +/// # use candid::types::bounded_vec::{BoundedVec, UNBOUNDED}; +/// // E.g., a user of your service sends candid-encoded bytes: +/// let too_long = vec![13u64; 11]; +/// let bytes_too_long = Encode!(&too_long).unwrap(); +/// // Your service should decode the untrusted bytes with care, by decoding to BoundedVec<10, _, _, _>. +/// // Since the user sent 11 elements and you allow at most 10, this will fail, keeping the service safe from certain exploits. +/// let error = +/// Decode!(&bytes_too_long, BoundedVec<10, UNBOUNDED, UNBOUNDED, u64>).unwrap_err(); +/// assert!(format!("{error:?}").contains("exceeds maximum allowed")); +/// ``` +/// +#[derive(Clone, Eq, PartialEq, Debug, Default)] +pub struct BoundedVec< + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T, +>(Vec); + +impl< + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T: CandidType, + > CandidType + for BoundedVec +{ + fn _ty() -> super::Type { + TypeInner::Vec(T::_ty()).into() + } + + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: super::Serializer, + { + self.0.idl_serialize(serializer) + } +} + +impl< + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T, + > BoundedVec +{ + pub fn new(data: Vec) -> Self { + assert!( + MAX_ALLOWED_LEN != UNBOUNDED + || MAX_ALLOWED_TOTAL_DATA_SIZE != UNBOUNDED + || MAX_ALLOWED_ELEMENT_DATA_SIZE != UNBOUNDED, + "BoundedVec must be bounded by at least one parameter." + ); + + Self(data) + } + + pub fn get(&self) -> &Vec { + &self.0 + } +} + +impl< + 'de, + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T: Deserialize<'de> + DataSize, + > Deserialize<'de> + for BoundedVec +{ + fn deserialize>(deserializer: D) -> Result { + struct SeqVisitor< + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T, + > { + _marker: std::marker::PhantomData, + } + + use serde::de::{SeqAccess, Visitor}; + + impl< + 'de, + const MAX_ALLOWED_LEN: usize, + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize, + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize, + T: Deserialize<'de> + DataSize, + > Visitor<'de> + for SeqVisitor< + MAX_ALLOWED_LEN, + MAX_ALLOWED_TOTAL_DATA_SIZE, + MAX_ALLOWED_ELEMENT_DATA_SIZE, + T, + > + { + type Value = BoundedVec< + MAX_ALLOWED_LEN, + MAX_ALLOWED_TOTAL_DATA_SIZE, + MAX_ALLOWED_ELEMENT_DATA_SIZE, + T, + >; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "{}", + describe_sequence( + MAX_ALLOWED_LEN, + MAX_ALLOWED_TOTAL_DATA_SIZE, + MAX_ALLOWED_ELEMENT_DATA_SIZE, + ) + ) + } + + fn visit_seq(self, mut seq: S) -> Result + where + S: SeqAccess<'de>, + { + let mut total_data_size = 0; + let mut elements = if MAX_ALLOWED_LEN == UNBOUNDED { + Vec::new() + } else { + Vec::with_capacity(MAX_ALLOWED_LEN) + }; + while let Some(element) = seq.next_element::()? { + if elements.len() >= MAX_ALLOWED_LEN { + return Err(serde::de::Error::custom(format!( + "The number of elements exceeds maximum allowed {MAX_ALLOWED_LEN}" + ))); + } + // Check that the new element data size is below the maximum allowed limit. + let new_element_data_size = element.data_size(); + if new_element_data_size > MAX_ALLOWED_ELEMENT_DATA_SIZE { + return Err(serde::de::Error::custom(format!( + "The single element data size exceeds maximum allowed {MAX_ALLOWED_ELEMENT_DATA_SIZE}" + ))); + } + // Check that the new total data size (including new element data size) + // is below the maximum allowed limit. + let new_total_data_size = total_data_size + new_element_data_size; + if new_total_data_size > MAX_ALLOWED_TOTAL_DATA_SIZE { + return Err(serde::de::Error::custom(format!( + "The total data size exceeds maximum allowed {MAX_ALLOWED_TOTAL_DATA_SIZE}" + ))); + } + total_data_size = new_total_data_size; + elements.push(element); + } + Ok(BoundedVec::new(elements)) + } + } + + deserializer.deserialize_seq(SeqVisitor::< + MAX_ALLOWED_LEN, + MAX_ALLOWED_TOTAL_DATA_SIZE, + MAX_ALLOWED_ELEMENT_DATA_SIZE, + T, + > { + _marker: std::marker::PhantomData, + }) + } +} + +fn describe_sequence( + max_allowed_len: usize, + max_allowed_total_data_size: usize, + max_allowed_element_data_size: usize, +) -> String { + let mut msg = String::new(); + if max_allowed_len != UNBOUNDED { + msg.push_str(&format!("max {max_allowed_len} elements")); + }; + if max_allowed_total_data_size != UNBOUNDED { + if !msg.is_empty() { + msg.push_str(", "); + } + msg.push_str(&format!("max {max_allowed_total_data_size} bytes total")); + }; + if max_allowed_element_data_size != UNBOUNDED { + if !msg.is_empty() { + msg.push_str(", "); + } + msg.push_str(&format!( + "max {max_allowed_element_data_size} bytes per element" + )); + }; + format!("a sequence with {msg}") +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Decode, Encode}; + + #[test] + fn test_describe_sequence() { + assert_eq!( + describe_sequence(42, UNBOUNDED, UNBOUNDED), + "a sequence with max 42 elements".to_string() + ); + assert_eq!( + describe_sequence(UNBOUNDED, 256, UNBOUNDED), + "a sequence with max 256 bytes total".to_string(), + ); + assert_eq!( + describe_sequence(UNBOUNDED, UNBOUNDED, 64), + "a sequence with max 64 bytes per element".to_string(), + ); + assert_eq!( + describe_sequence(42, 256, UNBOUNDED), + "a sequence with max 42 elements, max 256 bytes total".to_string(), + ); + assert_eq!( + describe_sequence(42, UNBOUNDED, 64), + "a sequence with max 42 elements, max 64 bytes per element".to_string(), + ); + assert_eq!( + describe_sequence(UNBOUNDED, 256, 64), + "a sequence with max 256 bytes total, max 64 bytes per element".to_string(), + ); + assert_eq!( + describe_sequence(42, 256, 64), + "a sequence with max 42 elements, max 256 bytes total, max 64 bytes per element" + .to_string(), + ); + } + + #[test] + #[should_panic] + fn test_not_bounded_vector_fails() { + type NotBoundedVec = BoundedVec; + + let _ = NotBoundedVec::new(vec![1, 2, 3]); + } + + #[test] + fn test_bounded_vector_lengths() { + // This test verifies that the structures containing BoundedVec correctly + // throw an error when the number of elements exceeds the maximum allowed. + type BoundedLen = BoundedVec; + + const MAX_ALLOWED_LEN: usize = 30; + const TEST_START: usize = 20; + const TEST_END: usize = 40; + for i in TEST_START..=TEST_END { + // Arrange. + let data = BoundedLen::new(vec![42; i]); + + // Act. + let bytes = Encode!(&data).unwrap(); + let result = Decode!(&bytes, BoundedLen); + + // Assert. + if i <= MAX_ALLOWED_LEN { + // Verify decoding without errors for allowed sizes. + assert!(result.is_ok()); + assert_eq!(result.unwrap(), data); + } else { + // Verify decoding with errors for disallowed sizes. + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!( + format!("{error:?}").contains(&format!( + "Deserialize error: The number of elements exceeds maximum allowed {MAX_ALLOWED_LEN}" + )), + "Actual: {}", + error + ); + } + } + } + + #[test] + fn test_bounded_vector_total_data_sizes() { + // This test verifies that the structures containing BoundedVec correctly + // throw an error when the total data size exceeds the maximum allowed. + const MAX_ALLOWED_TOTAL_DATA_SIZE: usize = 100; + const ELEMENT_SIZE: usize = 37; + // Assert element size is not a multiple of total size. + assert_ne!(MAX_ALLOWED_TOTAL_DATA_SIZE % ELEMENT_SIZE, 0); + for aimed_total_size in 64..=256 { + // Arrange. + type BoundedSize = + BoundedVec>; + let element = vec![b'a'; ELEMENT_SIZE - std::mem::size_of::>()]; + let elements_count = aimed_total_size / element.data_size(); + let data = BoundedSize::new(vec![element; elements_count]); + let actual_total_size = data.get().data_size(); + + // Act. + let bytes = Encode!(&data).unwrap(); + let result = Decode!(&bytes, BoundedSize); + + // Assert. + if actual_total_size <= MAX_ALLOWED_TOTAL_DATA_SIZE { + // Verify decoding without errors for allowed sizes. + assert!(result.is_ok()); + assert_eq!(result.unwrap(), data); + } else { + // Verify decoding with errors for disallowed sizes. + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!( + format!("{error:?}").contains(&format!( + "Deserialize error: The total data size exceeds maximum allowed {MAX_ALLOWED_TOTAL_DATA_SIZE}" + )), + "Actual: {}", + error + ); + } + } + } + + #[test] + fn test_bounded_vector_element_data_sizes() { + // This test verifies that the structures containing BoundedVec correctly + // throw an error when the element data size exceeds the maximum allowed. + const MAX_ALLOWED_ELEMENT_DATA_SIZE: usize = 100; + for element_size in 64..=256 { + // Arrange. + type BoundedSize = + BoundedVec>; + let element = vec![b'a'; element_size - std::mem::size_of::>()]; + let data = BoundedSize::new(vec![element; 42]); + + // Act. + let bytes = Encode!(&data).unwrap(); + let result = Decode!(&bytes, BoundedSize); + + // Assert. + if element_size <= MAX_ALLOWED_ELEMENT_DATA_SIZE { + // Verify decoding without errors for allowed sizes. + assert!(result.is_ok()); + assert_eq!(result.unwrap(), data); + } else { + // Verify decoding with errors for disallowed sizes. + assert!(result.is_err()); + let error = result.unwrap_err(); + assert!( + format!("{error:?}").contains(&format!( + "Deserialize error: The single element data size exceeds maximum allowed {MAX_ALLOWED_ELEMENT_DATA_SIZE}" + )), + "Actual: {}", + error + ); + } + } + } +} + +mod data_size { + // We implement a standalone trait `DataSize` here and use it + // instead of `CountBytes`. + + /// Trait to reasonably estimate the memory usage of a value in bytes. + /// + /// Default implementation returns zero. + pub trait DataSize { + /// Default implementation returns zero. + fn data_size(&self) -> usize { + 0 + } + } + + impl DataSize for u8 { + fn data_size(&self) -> usize { + std::mem::size_of::() + } + } + + impl DataSize for [u8] { + fn data_size(&self) -> usize { + std::mem::size_of_val(self) + } + } + + impl DataSize for u64 { + fn data_size(&self) -> usize { + std::mem::size_of::() + } + } + + impl DataSize for &str { + fn data_size(&self) -> usize { + self.as_bytes().data_size() + } + } + + impl DataSize for String { + fn data_size(&self) -> usize { + self.as_bytes().data_size() + } + } + + impl DataSize for Vec { + fn data_size(&self) -> usize { + std::mem::size_of::() + self.iter().map(|x| x.data_size()).sum::() + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_data_size_u8() { + assert_eq!(0_u8.data_size(), 1); + assert_eq!(42_u8.data_size(), 1); + } + + #[test] + fn test_data_size_u8_slice() { + let a: [u8; 0] = []; + assert_eq!(a.data_size(), 0); + assert_eq!([1_u8].data_size(), 1); + assert_eq!([1_u8, 2_u8].data_size(), 2); + } + + #[test] + fn test_data_size_u64() { + assert_eq!(0_u64.data_size(), 8); + assert_eq!(42_u64.data_size(), 8); + } + + #[test] + fn test_data_size_u8_vec() { + let base = 24; + assert_eq!(Vec::::from([]).data_size(), base); + assert_eq!(Vec::::from([1]).data_size(), base + 1); + assert_eq!(Vec::::from([1, 2]).data_size(), base + 2); + } + + #[test] + fn test_data_size_str() { + assert_eq!("a".data_size(), 1); + assert_eq!("ab".data_size(), 2); + } + + #[test] + fn test_data_size_string() { + assert_eq!(String::from("a").data_size(), 1); + assert_eq!(String::from("ab").data_size(), 2); + for size_bytes in 0..1_024 { + assert_eq!( + String::from_utf8(vec![b'x'; size_bytes]) + .unwrap() + .data_size(), + size_bytes + ); + } + } + } +} diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 49af606db..367780e0f 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -19,6 +19,7 @@ pub use self::internal::{ }; pub use type_env::TypeEnv; +pub mod bounded_vec; pub mod leb128; #[cfg(feature = "bignum")] pub mod number; From c339ac1b4985099e8611e4db0bf0a296a73f88da Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Fri, 20 Feb 2026 13:10:46 -0800 Subject: [PATCH 13/43] chore: Release candid v0.10.23 (#704) --- CHANGELOG.md | 4 +++- Cargo.lock | 8 ++++---- rust/bench/Cargo.lock | 4 ++-- rust/candid/Cargo.toml | 4 ++-- rust/candid_derive/Cargo.toml | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24223d5ce..32169a532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## Unreleased -### Candid +## 2026-02-20 + +### Candid 0.10.23 * Non-breaking changes: + Add `BoundedVec` type to `candid::types::bounded_vec` for bounding a vector by number of elements, total data size, and per-element data size during deserialization diff --git a/Cargo.lock b/Cargo.lock index 709b177ba..41eb9de95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,13 +209,13 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.22" +version = "0.10.23" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.22", + "candid_derive 0.10.23", "candid_parser 0.3.0", "hex", "ic_principal 0.1.2", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.22" +version = "0.10.23" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -280,7 +280,7 @@ version = "0.3.0" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.22", + "candid 0.10.23", "codespan-reporting", "console", "convert_case", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index 089bbfa8b..b14f44447 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.22" +version = "0.10.23" dependencies = [ "anyhow", "binread", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.22" +version = "0.10.23" dependencies = [ "lazy_static", "proc-macro2", diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index 143e62f9e..e67ffccd4 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.22" +version = "0.10.23" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.22" } +candid_derive = { path = "../candid_derive", version = "=0.10.23" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index b13e65f9b..1e7ef7cb8 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.22" +version = "0.10.23" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] From ba72cf4dbd42c182d7bfd281a718fd622a7a339e Mon Sep 17 00:00:00 2001 From: Dimitris Sarlis Date: Fri, 27 Feb 2026 20:50:51 +0200 Subject: [PATCH 14/43] feat: Implement DataSize for Principal (#705) --- CHANGELOG.md | 7 +++++++ Cargo.lock | 8 ++++---- rust/bench/Cargo.lock | 4 ++-- rust/candid/Cargo.toml | 4 ++-- rust/candid/src/types/bounded_vec.rs | 9 ++++++--- rust/candid_derive/Cargo.toml | 2 +- rust/ic_principal/tests/principal.rs | 19 +++++++++++++++++++ 7 files changed, 41 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32169a532..ef6b4a43b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## 2026-02-27 + +### Candid 0.10.24 + +* Non-breaking changes: + + Implement `DataSize` for `Principal`, enabling `Principal` as an element type in `BoundedVec` + ## 2026-02-20 ### Candid 0.10.23 diff --git a/Cargo.lock b/Cargo.lock index 41eb9de95..319ab82af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,13 +209,13 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.23" +version = "0.10.24" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.23", + "candid_derive 0.10.24", "candid_parser 0.3.0", "hex", "ic_principal 0.1.2", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.23" +version = "0.10.24" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -280,7 +280,7 @@ version = "0.3.0" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.23", + "candid 0.10.24", "codespan-reporting", "console", "convert_case", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index b14f44447..2400c4bc3 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.23" +version = "0.10.24" dependencies = [ "anyhow", "binread", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.23" +version = "0.10.24" dependencies = [ "lazy_static", "proc-macro2", diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index e67ffccd4..afa6fb959 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.23" +version = "0.10.24" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.23" } +candid_derive = { path = "../candid_derive", version = "=0.10.24" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" diff --git a/rust/candid/src/types/bounded_vec.rs b/rust/candid/src/types/bounded_vec.rs index c5ca0d4ae..d0a850fc7 100644 --- a/rust/candid/src/types/bounded_vec.rs +++ b/rust/candid/src/types/bounded_vec.rs @@ -367,9 +367,6 @@ mod tests { } mod data_size { - // We implement a standalone trait `DataSize` here and use it - // instead of `CountBytes`. - /// Trait to reasonably estimate the memory usage of a value in bytes. /// /// Default implementation returns zero. @@ -416,6 +413,12 @@ mod data_size { } } + impl DataSize for ic_principal::Principal { + fn data_size(&self) -> usize { + self.as_slice().len() + } + } + #[cfg(test)] mod tests { use super::*; diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index 1e7ef7cb8..2e551bd51 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.23" +version = "0.10.24" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] diff --git a/rust/ic_principal/tests/principal.rs b/rust/ic_principal/tests/principal.rs index bf9b0438b..1732fa156 100644 --- a/rust/ic_principal/tests/principal.rs +++ b/rust/ic_principal/tests/principal.rs @@ -314,3 +314,22 @@ fn rangemap_steplite_impl_works() { assert!(set.contains(&p2)); assert!(!set.contains(&Principal::from_slice(&[0, 1, 4, 0]))); } + +#[test] +fn as_slice_len_principal() { + let anonymous_principal = Principal::anonymous(); + assert_eq!(anonymous_principal.as_slice().len(), 1); + + let management_canister = Principal::management_canister(); + assert_eq!(management_canister.as_slice().len(), 0); + + let key = b"42"; + let self_authenticating = Principal::self_authenticating(key); + assert_eq!(self_authenticating.as_slice().len(), 29); + + let from_slice = Principal::from_slice(&TEST_CASE_BYTES); + assert_eq!(from_slice.as_slice().len(), 9); + + let from_text = Principal::from_text(TEST_CASE_TEXT).unwrap(); + assert_eq!(from_text.as_slice().len(), 9); +} From b51c0c5c4f43e97f8fbf5e3d80224b97e62a0cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 16:57:20 +0100 Subject: [PATCH 15/43] feat(candid): preserve doc comments on exported types and fields (#707) ### Solution - Add `TypeDoc`, `FieldDoc`, and `TypeDocs` types to carry doc metadata alongside the type graph - Extend `CandidType` derive to extract Rust doc comments via new `_ty_doc()` hook; store per-TypeId in thread-local DOC_ENV - Update pretty-printer to render docs above type definitions, record fields, and variant members ### Details - Docs flow through `TypeContainer` which maps Rust TypeId docs to final Candid export names - Tuples with field docs fall back to explicit numeric field syntax ### Meta - This is needed for my work on Immutable Object Storage. I currently have some hacks to add doc comments to .did files, but everyone would benefit if this was fixed properly. --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- CHANGELOG.md | 3 + rust/candid/src/pretty/candid.rs | 98 ++++++++++- rust/candid/src/types/internal.rs | 66 +++++++- rust/candid/src/types/mod.rs | 9 +- rust/candid/tests/types.rs | 117 +++++++++++++ rust/candid_derive/src/derive.rs | 206 +++++++++++++++++++---- rust/candid_derive/src/docs.rs | 23 +++ rust/candid_derive/src/func.rs | 27 +-- rust/candid_derive/src/lib.rs | 1 + spec/Type-doc-comments.md | 270 ++++++++++++++++++++++++++++++ 10 files changed, 749 insertions(+), 71 deletions(-) create mode 100644 rust/candid_derive/src/docs.rs create mode 100644 spec/Type-doc-comments.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ef6b4a43b..81bdcd1ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Non-breaking changes: + + Preserve Rust doc comments on exported Candid types, record fields, and variant members when generating `.did` files via `#[derive(CandidType)]` + ## 2026-02-27 ### Candid 0.10.24 diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index 062b5d3a0..cb04d68bc 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -1,7 +1,10 @@ use std::collections::HashMap; use crate::pretty::utils::*; -use crate::types::{Field, FuncMode, Function, Label, SharedLabel, Type, TypeEnv, TypeInner}; +use crate::types::{ + Field, FieldDoc, FuncMode, Function, Label, SharedLabel, Type, TypeDoc, TypeDocs, TypeEnv, + TypeInner, +}; use pretty::RcDoc; static KEYWORDS: [&str; 30] = [ @@ -125,6 +128,12 @@ pub fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> { lines(docs.iter().map(|line| RcDoc::text("// ").append(line))) } +fn maybe_pp_docs<'a>(docs: Option<&'a [String]>) -> RcDoc<'a> { + docs.filter(|docs| !docs.is_empty()) + .map(pp_docs) + .unwrap_or_else(RcDoc::nil) +} + /// This function is kept for backward compatibility. /// /// It is recommended to use [`pp_label_raw`] instead, which accepts a [`Label`]. @@ -153,6 +162,56 @@ fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc<'_> { enclose_space("{", concat(fields, ";"), "}") } +fn pp_field_with_doc<'a>( + field: &'a Field, + is_variant: bool, + doc: Option<&'a FieldDoc>, +) -> RcDoc<'a> { + let docs = maybe_pp_docs(doc.map(|doc| doc.docs.as_slice())); + let ty_doc = if is_variant && *field.ty == TypeInner::Null { + RcDoc::nil() + } else { + kwd(" :").append(pp_ty_with_doc( + &field.ty, + doc.and_then(|doc| doc.ty.as_deref()), + )) + }; + docs.append(pp_label_raw(&field.id)).append(ty_doc) +} + +fn pp_fields_with_doc<'a>( + fs: &'a [Field], + is_variant: bool, + doc: Option<&'a TypeDoc>, +) -> RcDoc<'a> { + let fields = fs.iter().map(|field| { + let field_doc = doc.and_then(|doc| doc.fields.get(&field.id.get_id())); + pp_field_with_doc(field, is_variant, field_doc) + }); + enclose_space("{", concat(fields, ";"), "}") +} + +fn has_field_docs(doc: Option<&TypeDoc>) -> bool { + doc.map(|doc| doc.fields.values().any(|field| !field.is_empty())) + .unwrap_or(false) +} + +fn pp_ty_with_doc<'a>(ty: &'a Type, doc: Option<&'a TypeDoc>) -> RcDoc<'a> { + use TypeInner::*; + match ty.as_ref() { + Record(ref fs) => { + if ty.is_tuple() && !has_field_docs(doc) { + let tuple = concat(fs.iter().map(|f| pp_ty_with_doc(&f.ty, None)), ";"); + kwd("record").append(enclose_space("{", tuple, "}")) + } else { + kwd("record").append(pp_fields_with_doc(fs, false, doc)) + } + } + Variant(ref fs) => kwd("variant").append(pp_fields_with_doc(fs, true, doc)), + ty => pp_ty_inner(ty), + } +} + pub fn pp_function(func: &Function) -> RcDoc<'_> { let args = pp_args(&func.args); let rets = pp_rets(&func.rets); @@ -204,7 +263,7 @@ fn pp_service<'a>(serv: &'a [(String, Type)], docs: Option<&'a DocComments>) -> enclose_space("{", doc, "}") } -fn pp_defs(env: &TypeEnv) -> RcDoc<'_> { +fn pp_defs_plain(env: &TypeEnv) -> RcDoc<'_> { lines(env.0.iter().map(|(id, ty)| { kwd("type") .append(ident(id)) @@ -214,6 +273,18 @@ fn pp_defs(env: &TypeEnv) -> RcDoc<'_> { })) } +fn pp_defs<'a>(env: &'a TypeEnv, docs: &'a DocComments) -> RcDoc<'a> { + lines(env.0.iter().map(|(id, ty)| { + let type_doc = docs.lookup_type_def(id); + maybe_pp_docs(type_doc.map(|doc| doc.docs.as_slice())) + .append(kwd("type")) + .append(ident(id)) + .append(kwd("=")) + .append(pp_ty_with_doc(ty, type_doc)) + .append(";") + })) +} + fn pp_class<'a>(args: &'a [Type], t: &'a Type, docs: Option<&'a DocComments>) -> RcDoc<'a> { let doc = pp_args(args).append(" ->").append(RcDoc::space()); match t.as_ref() { @@ -234,13 +305,14 @@ fn pp_actor<'a>(ty: &'a Type, docs: &'a DocComments) -> RcDoc<'a> { /// Pretty-prints the initialization arguments for a Candid actor. pub fn pp_init_args<'a>(env: &'a TypeEnv, args: &'a [Type]) -> RcDoc<'a> { - pp_defs(env).append(pp_args(args)) + pp_defs_plain(env).append(pp_args(args)) } /// Collects doc comments that can be passed to the [compile_with_docs] function. #[derive(Default, Debug)] pub struct DocComments { service_methods: HashMap>, + type_defs: HashMap, } impl DocComments { @@ -255,6 +327,22 @@ impl DocComments { pub fn lookup_service_method(&self, method: &str) -> Option<&Vec> { self.service_methods.get(method) } + + pub fn add_type_def(&mut self, name: String, doc: TypeDoc) { + if !doc.is_empty() { + self.type_defs.insert(name, doc); + } + } + + pub fn lookup_type_def(&self, name: &str) -> Option<&TypeDoc> { + self.type_defs.get(name) + } + + pub fn extend_types(&mut self, docs: TypeDocs) { + for (name, doc) in docs.named { + self.add_type_def(name, doc); + } + } } pub fn compile(env: &TypeEnv, actor: &Option) -> String { @@ -281,9 +369,9 @@ pub fn compile(env: &TypeEnv, actor: &Option) -> String { /// ``` pub fn compile_with_docs(env: &TypeEnv, actor: &Option, docs: &DocComments) -> String { match actor { - None => pp_defs(env).pretty(LINE_WIDTH).to_string(), + None => pp_defs(env, docs).pretty(LINE_WIDTH).to_string(), Some(actor) => { - let defs = pp_defs(env); + let defs = pp_defs(env, docs); let actor = kwd("service :").append(pp_actor(actor, docs)); let doc = defs.append(actor); doc.pretty(LINE_WIDTH).to_string() diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index b4a862ec4..6e82e15cf 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -71,6 +71,39 @@ impl TypeName { } } +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct TypeDocs { + pub named: BTreeMap, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct TypeDoc { + pub docs: Vec, + pub fields: BTreeMap, +} + +impl TypeDoc { + pub fn is_empty(&self) -> bool { + self.docs.is_empty() && self.fields.is_empty() + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct FieldDoc { + pub docs: Vec, + pub ty: Option>, +} + +impl FieldDoc { + pub fn is_empty(&self) -> bool { + self.docs.is_empty() + && match self.ty.as_deref() { + None => true, + Some(doc) => doc.is_empty(), + } + } +} + /// Used for `candid_derive::export_service` to generate `TypeEnv` from `Type`. /// /// It performs a global rewriting of `Type` to resolve: @@ -85,11 +118,13 @@ impl TypeName { #[derive(Default)] pub struct TypeContainer { pub env: crate::TypeEnv, + pub docs: TypeDocs, } impl TypeContainer { pub fn new() -> Self { TypeContainer { env: crate::TypeEnv::new(), + docs: TypeDocs::default(), } } pub fn add(&mut self) -> Type { @@ -115,8 +150,10 @@ impl TypeContainer { } let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { - self.env.0.insert(id.to_string(), res); - TypeInner::Var(id.to_string()) + let name = id.to_string(); + self.env.0.insert(name.clone(), res); + self.remember_named_doc(&id, &name); + TypeInner::Var(name) } else { // if the type is part of an enum, the id won't be recorded. // we want to inline the type in this case. @@ -135,8 +172,10 @@ impl TypeContainer { .into(); let id = ID.with(|n| n.borrow().get(t).cloned()); if let Some(id) = id { - self.env.0.insert(id.to_string(), res); - TypeInner::Var(id.to_string()) + let name = id.to_string(); + self.env.0.insert(name.clone(), res); + self.remember_named_doc(&id, &name); + TypeInner::Var(name) } else { return res; } @@ -145,6 +184,7 @@ impl TypeContainer { let name = id.to_string(); let ty = ENV.with(|e| e.borrow().get(id).unwrap().clone()); self.env.0.insert(id.to_string(), ty); + self.remember_named_doc(id, &name); TypeInner::Var(name) } TypeInner::Func(func) => TypeInner::Func(Function { @@ -164,6 +204,14 @@ impl TypeContainer { } .into() } + + fn remember_named_doc(&mut self, id: &TypeId, name: &str) { + if let Some(doc) = find_type_doc(id) { + if !doc.is_empty() { + self.docs.named.entry(name.to_string()).or_insert(doc); + } + } + } } #[derive(Debug, PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] @@ -644,6 +692,7 @@ pub fn unroll(t: &Type) -> Type { thread_local! { static ENV: RefCell> = const { RefCell::new(BTreeMap::new()) }; + static DOC_ENV: RefCell> = const { RefCell::new(BTreeMap::new()) }; // only used for TypeContainer static ID: RefCell> = const { RefCell::new(BTreeMap::new()) }; static NAME: RefCell = RefCell::new(TypeName::default()); @@ -653,6 +702,10 @@ pub fn find_type(id: &TypeId) -> Option { ENV.with(|e| e.borrow().get(id).cloned()) } +pub fn find_type_doc(id: &TypeId) -> Option { + DOC_ENV.with(|e| e.borrow().get(id).cloned()) +} + // only for debugging #[allow(dead_code)] pub(crate) fn show_env() { @@ -664,6 +717,7 @@ pub(crate) fn env_add(id: TypeId, t: Type) { } pub fn env_clear() { ENV.with(|e| e.borrow_mut().clear()); + DOC_ENV.with(|e| e.borrow_mut().clear()); } pub(crate) fn env_id(id: TypeId, t: Type) { @@ -684,6 +738,10 @@ pub(crate) fn env_id(id: TypeId, t: Type) { }); } +pub(crate) fn env_doc(id: TypeId, doc: TypeDoc) { + DOC_ENV.with(|e| e.borrow_mut().insert(id, doc)); +} + pub fn get_type(_v: &T) -> Type where T: CandidType, diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 367780e0f..42b905473 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -15,7 +15,8 @@ pub mod type_env; pub mod value; pub use self::internal::{ - get_type, Field, FuncMode, Function, Label, SharedLabel, Type, TypeId, TypeInner, + get_type, Field, FieldDoc, FuncMode, Function, Label, SharedLabel, Type, TypeDoc, TypeDocs, + TypeId, TypeInner, }; pub use type_env::TypeEnv; @@ -44,7 +45,8 @@ pub trait CandidType { self::internal::env_add(id.clone(), TypeInner::Unknown.into()); let t = Self::_ty(); self::internal::env_add(id.clone(), t.clone()); - self::internal::env_id(id, t.clone()); + self::internal::env_id(id.clone(), t.clone()); + self::internal::env_doc(id, Self::_ty_doc()); t } } @@ -52,6 +54,9 @@ pub trait CandidType { TypeId::of::() } fn _ty() -> Type; + fn _ty_doc() -> internal::TypeDoc { + internal::TypeDoc::default() + } // only serialize the value encoding fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> where diff --git a/rust/candid/tests/types.rs b/rust/candid/tests/types.rs index 688fcf2f9..5de3ab2df 100644 --- a/rust/candid/tests/types.rs +++ b/rust/candid/tests/types.rs @@ -339,3 +339,120 @@ fn test_counter() { let expected = "service : { inc : () -> (); read : () -> (nat64) query; set : (nat64) -> () }"; assert_eq!(expected, __export_service()); } + +#[test] +fn test_export_service_type_and_field_docs() { + /// Status docs. + #[derive(CandidType, Deserialize)] + enum Status { + /// Account is active. + Active, + /// Account is banned. + Banned(String), + } + + /// User payload docs. + #[derive(CandidType, Deserialize)] + struct User { + /// Stable identifier. + id: u64, + /// Display name. + name: String, + } + + /// Lookup docs. + #[candid_method(query)] + fn lookup(_: User) -> Status { + unreachable!() + } + + candid::export_service!(); + let expected = r#"// Status docs. +type Status = variant { + // Account is active. + Active; + // Account is banned. + Banned : text; +}; +// User payload docs. +type User = record { + // Stable identifier. + id : nat64; + // Display name. + name : text; +}; +service : { + // Lookup docs. + lookup : (User) -> (Status) query; +}"#; + assert_eq!(expected, __export_service()); +} + +#[test] +fn test_exported_did_parses_with_docs_attached() { + use candid_parser::syntax::{Dec, IDLProg, IDLType}; + + /// Status docs. + #[derive(CandidType, Deserialize)] + enum Status { + /// Account is active. + Active, + /// Account is banned. + Banned(String), + } + + /// User payload docs. + #[derive(CandidType, Deserialize)] + struct User { + /// Stable identifier. + id: u64, + /// Display name. + name: String, + } + + /// Lookup docs. + #[candid_method(query)] + fn lookup(_: User) -> Status { + unreachable!() + } + + candid::export_service!(); + let ast: IDLProg = __export_service().parse().unwrap(); + + let status = ast + .decs + .iter() + .find_map(|dec| match dec { + Dec::TypD(binding) if binding.id == "Status" => Some(binding), + _ => None, + }) + .unwrap(); + assert_eq!(status.docs, vec!["Status docs."]); + let IDLType::VariantT(status_fields) = &status.typ else { + panic!("expected Status to be a variant"); + }; + assert_eq!(status_fields[0].docs, vec!["Account is active."]); + assert_eq!(status_fields[1].docs, vec!["Account is banned."]); + + let user = ast + .decs + .iter() + .find_map(|dec| match dec { + Dec::TypD(binding) if binding.id == "User" => Some(binding), + _ => None, + }) + .unwrap(); + assert_eq!(user.docs, vec!["User payload docs."]); + let IDLType::RecordT(user_fields) = &user.typ else { + panic!("expected User to be a record"); + }; + assert_eq!(user_fields[0].docs, vec!["Stable identifier."]); + assert_eq!(user_fields[1].docs, vec!["Display name."]); + + let actor = ast.actor.unwrap(); + let IDLType::ServT(methods) = &actor.typ else { + panic!("expected actor to be a service"); + }; + assert_eq!(methods[0].id, "lookup"); + assert_eq!(methods[0].docs, vec!["Lookup docs."]); +} diff --git a/rust/candid_derive/src/derive.rs b/rust/candid_derive/src/derive.rs index 7f2838ff1..8da7fa26f 100644 --- a/rust/candid_derive/src/derive.rs +++ b/rust/candid_derive/src/derive.rs @@ -1,4 +1,4 @@ -use super::{candid_path, get_lit_str, idl_hash}; +use super::{candid_path, docs::extract_doc_comments, get_lit_str, idl_hash}; use proc_macro2::TokenStream; use quote::quote; use std::collections::BTreeSet; @@ -11,14 +11,22 @@ pub(crate) fn derive_idl_type( custom_candid_path: &Option, ) -> TokenStream { let candid = candid_path(custom_candid_path); + let root_docs = extract_doc_comments(&input.attrs); let name = input.ident; let generics = add_trait_bounds(input.generics, custom_candid_path); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let (ty_body, ser_body) = match input.data { - Data::Enum(ref data) => enum_from_ast(&name, &data.variants, custom_candid_path), + let (ty_body, ty_doc_body, ser_body) = match input.data { + Data::Enum(ref data) => { + enum_from_ast(&name, &root_docs, &data.variants, custom_candid_path) + } Data::Struct(ref data) => { - let (ty, idents, is_bytes, _) = struct_from_ast(&data.fields, custom_candid_path); - (ty, serialize_struct(&idents, &is_bytes, custom_candid_path)) + let mut shape = struct_from_ast(&data.fields, custom_candid_path); + shape.doc.docs = root_docs; + ( + shape.ty, + quote_type_doc(&candid, &shape.doc), + serialize_struct(&shape.members, &shape.is_bytes, custom_candid_path), + ) } Data::Union(_) => unimplemented!("doesn't derive union type"), }; @@ -27,6 +35,9 @@ pub(crate) fn derive_idl_type( fn _ty() -> #candid::types::Type { #ty_body } + fn _ty_doc() -> #candid::types::TypeDoc { + #ty_doc_body + } fn id() -> #candid::types::TypeId { #candid::types::TypeId::of::<#name #ty_generics>() } fn idl_serialize<__S>(&self, __serializer: __S) -> ::std::result::Result<(), __S::Error> @@ -41,11 +52,50 @@ pub(crate) fn derive_idl_type( gen } +#[derive(Clone, Default)] +struct TypeDocSpec { + docs: Vec, + fields: Vec, +} + +impl TypeDocSpec { + fn is_empty(&self) -> bool { + self.docs.is_empty() && self.fields.iter().all(DocField::is_empty) + } +} + +#[derive(Clone)] +struct DocField { + key: u32, + docs: Vec, + ty: Option>, +} + +impl DocField { + fn is_empty(&self) -> bool { + self.docs.is_empty() + && match self.ty.as_deref() { + None => true, + Some(doc) => doc.is_empty(), + } + } +} + +struct Shape { + ty: TokenStream, + doc: TypeDocSpec, + members: Vec, + is_bytes: Vec, + style: Style, +} + struct Variant { real_ident: syn::Ident, renamed_ident: String, hash: u32, ty: TokenStream, + docs: Vec, + payload_doc: Option, members: Vec, with_bytes: bool, style: Style, @@ -91,9 +141,10 @@ impl Variant { fn enum_from_ast( name: &syn::Ident, + root_docs: &[String], variants: &Punctuated, custom_candid_path: &Option, -) -> (TokenStream, TokenStream) { +) -> (TokenStream, TokenStream, TokenStream) { let mut fs: Vec<_> = variants .iter() .map(|variant| { @@ -107,15 +158,17 @@ fn enum_from_ast( (id, hash) } }; - let (ty, idents, _, style) = struct_from_ast(&variant.fields, custom_candid_path); + let shape = struct_from_ast(&variant.fields, custom_candid_path); Variant { real_ident: id, renamed_ident, hash, - ty, - members: idents, + ty: shape.ty, + docs: extract_doc_comments(&variant.attrs), + payload_doc: (!shape.doc.is_empty()).then_some(shape.doc), + members: shape.members, with_bytes: attrs.with_bytes, - style, + style: shape.style, } }) .collect(); @@ -136,6 +189,20 @@ fn enum_from_ast( ] ).into() }; + let ty_doc_gen = quote_type_doc( + &candid, + &TypeDocSpec { + docs: root_docs.to_vec(), + fields: fs + .iter() + .map(|variant| DocField { + key: variant.hash, + docs: variant.docs.clone(), + ty: variant.payload_doc.clone().map(Box::new), + }) + .collect(), + }, + ); let id = fs.iter().map(|Variant { real_ident, .. }| { syn::parse_str::(&format!("{name}::{real_ident}")).unwrap() @@ -168,7 +235,7 @@ fn enum_from_ast( }; Ok(()) }; - (ty_gen, variant_gen) + (ty_gen, ty_doc_gen, variant_gen) } fn serialize_struct( @@ -192,41 +259,49 @@ fn serialize_struct( } } -fn struct_from_ast( - fields: &syn::Fields, - custom_candid_path: &Option, -) -> (TokenStream, Vec, Vec, Style) { +fn struct_from_ast(fields: &syn::Fields, custom_candid_path: &Option) -> Shape { let candid = candid_path(custom_candid_path); match *fields { syn::Fields::Named(ref fields) => { - let (fs, idents, is_bytes) = fields_from_ast(&fields.named, custom_candid_path); - ( - quote! { #candid::types::TypeInner::Record(#fs).into() }, - idents, + let (fs, doc, idents, is_bytes) = fields_from_ast(&fields.named, custom_candid_path); + Shape { + ty: quote! { #candid::types::TypeInner::Record(#fs).into() }, + doc, + members: idents, is_bytes, - Style::Struct, - ) + style: Style::Struct, + } } syn::Fields::Unnamed(ref fields) => { - let (fs, idents, is_bytes) = fields_from_ast(&fields.unnamed, custom_candid_path); + let (fs, doc, idents, is_bytes) = fields_from_ast(&fields.unnamed, custom_candid_path); if idents.len() == 1 { + // Newtypes are inlined to the inner type (no record wrapper), + // so field-level docs are not representable in the output. let newtype = derive_type(&fields.unnamed[0].ty, custom_candid_path); - (quote! { #newtype }, idents, is_bytes, Style::Tuple) + Shape { + ty: quote! { #newtype }, + doc: TypeDocSpec::default(), + members: idents, + is_bytes, + style: Style::Tuple, + } } else { - ( - quote! { #candid::types::TypeInner::Record(#fs).into() }, - idents, + Shape { + ty: quote! { #candid::types::TypeInner::Record(#fs).into() }, + doc, + members: idents, is_bytes, - Style::Tuple, - ) + style: Style::Tuple, + } } } - syn::Fields::Unit => ( - quote! { #candid::types::TypeInner::Null.into() }, - Vec::new(), - Vec::new(), - Style::Unit, - ), + syn::Fields::Unit => Shape { + ty: quote! { #candid::types::TypeInner::Null.into() }, + doc: TypeDocSpec::default(), + members: Vec::new(), + is_bytes: Vec::new(), + style: Style::Unit, + }, } } @@ -261,6 +336,7 @@ struct Field { renamed_ident: Ident, hash: u32, ty: TokenStream, + docs: Vec, with_bytes: bool, } @@ -327,7 +403,7 @@ fn get_attrs(attrs: &[syn::Attribute]) -> Attributes { fn fields_from_ast( fields: &Punctuated, custom_candid_path: &Option, -) -> (TokenStream, Vec, Vec) { +) -> (TokenStream, TypeDocSpec, Vec, Vec) { let candid = candid_path(custom_candid_path); let mut fs: Vec<_> = fields .iter() @@ -356,6 +432,7 @@ fn fields_from_ast( renamed_ident, hash, ty: derive_type(&field.ty, custom_candid_path), + docs: extract_doc_comments(&field.attrs), with_bytes: attrs.with_bytes, } }) @@ -390,7 +467,64 @@ fn fields_from_ast( .map(|Field { real_ident, .. }| real_ident.clone()) .collect(); let is_bytes: Vec<_> = fs.iter().map(|f| f.with_bytes).collect(); - (ty_gen, idents, is_bytes) + let doc = TypeDocSpec { + docs: Vec::new(), + fields: fs + .iter() + .map(|field| DocField { + key: field.hash, + docs: field.docs.clone(), + ty: None, + }) + .collect(), + }; + (ty_gen, doc, idents, is_bytes) +} + +fn quote_doc_lines(docs: &[String]) -> Vec { + docs.iter().map(|doc| quote! { #doc.to_string() }).collect() +} + +fn quote_type_doc(candid: &TokenStream, doc: &TypeDocSpec) -> TokenStream { + if doc.is_empty() { + return quote! { #candid::types::TypeDoc::default() }; + } + let docs = quote_doc_lines(&doc.docs); + let field_inserts: Vec<_> = doc + .fields + .iter() + .filter(|field| !field.is_empty()) + .map(|field| { + let key = field.key; + let field_doc = quote_field_doc(candid, field); + quote! { + doc.fields.insert(#key, #field_doc); + } + }) + .collect(); + quote! {{ + let mut doc = #candid::types::TypeDoc::default(); + doc.docs = vec![#(#docs,)*]; + #(#field_inserts)* + doc + }} +} + +fn quote_field_doc(candid: &TokenStream, doc: &DocField) -> TokenStream { + let docs = quote_doc_lines(&doc.docs); + let ty = match doc.ty.as_deref() { + Some(ty) if !ty.is_empty() => { + let ty = quote_type_doc(candid, ty); + quote! { Some(Box::new(#ty)) } + } + _ => quote! { None }, + }; + quote! { + #candid::types::FieldDoc { + docs: vec![#(#docs,)*], + ty: #ty, + } + } } fn derive_type(t: &syn::Type, custom_candid_path: &Option) -> TokenStream { diff --git a/rust/candid_derive/src/docs.rs b/rust/candid_derive/src/docs.rs new file mode 100644 index 000000000..4fb4b0e46 --- /dev/null +++ b/rust/candid_derive/src/docs.rs @@ -0,0 +1,23 @@ +use crate::get_lit_str; +use syn::Attribute; + +pub(crate) fn extract_doc_comments(attrs: &[Attribute]) -> Vec { + let mut docs = Vec::new(); + for attr in attrs { + if attr.path().is_ident("doc") { + if let syn::Meta::NameValue(meta) = &attr.meta { + if let Ok(lit) = get_lit_str(&meta.value) { + let doc_content = lit.value(); + if !doc_content.is_empty() { + for line in doc_content.lines() { + docs.push(line.trim().to_string()); + } + } else { + docs.push(String::new()); + } + } + } + } + } + docs +} diff --git a/rust/candid_derive/src/func.rs b/rust/candid_derive/src/func.rs index 577424564..65cb1cc95 100644 --- a/rust/candid_derive/src/func.rs +++ b/rust/candid_derive/src/func.rs @@ -1,10 +1,10 @@ -use super::{candid_path, get_lit_str}; +use super::{candid_path, docs::extract_doc_comments, get_lit_str}; use lazy_static::lazy_static; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use std::collections::BTreeMap; use std::sync::Mutex; -use syn::{Attribute, Error, ItemFn, Meta, Result, ReturnType, Signature, Type}; +use syn::{Error, ItemFn, Meta, Result, ReturnType, Signature, Type}; type RawArgs = Vec; type RawRets = Vec; @@ -169,6 +169,7 @@ pub(crate) fn export_service(path: Option) -> TokenStream { fn __export_service() -> String { #service #actor + docs.extend_types(env.docs); let result = #candid::pretty::candid::compile_with_docs(&env.env, &actor, &docs); format!("{}", result) } @@ -248,25 +249,3 @@ fn get_candid_attribute(attrs: Vec) -> Result { } Ok(res) } - -fn extract_doc_comments(attrs: &[Attribute]) -> Vec { - let mut docs = Vec::new(); - for attr in attrs { - if attr.path().is_ident("doc") { - if let syn::Meta::NameValue(meta) = &attr.meta { - if let Ok(lit) = get_lit_str(&meta.value) { - let doc_content = lit.value(); - if !doc_content.is_empty() { - for line in doc_content.lines() { - let trimmed = line.trim().to_string(); - docs.push(trimmed); - } - } else { - docs.push("".to_string()); - } - } - } - } - } - docs -} diff --git a/rust/candid_derive/src/lib.rs b/rust/candid_derive/src/lib.rs index eb26e4263..4b55ed7d3 100644 --- a/rust/candid_derive/src/lib.rs +++ b/rust/candid_derive/src/lib.rs @@ -2,6 +2,7 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, Result}; mod derive; +mod docs; mod func; #[proc_macro_derive(CandidType, attributes(candid_path))] diff --git a/spec/Type-doc-comments.md b/spec/Type-doc-comments.md new file mode 100644 index 000000000..ce266ba23 --- /dev/null +++ b/spec/Type-doc-comments.md @@ -0,0 +1,270 @@ +# Candid Type Doc Comments + +Date: Mar 12, 2026 + +## Motivation + +Rust canisters often use Rust doc comments as the primary source of interface +documentation. This works well for service methods exported with +`#[candid_method]`, because these doc comments are preserved when generating +textual Candid. However, the same is not true for data types exported through +`#[derive(CandidType)]`. + +As a result, generated `.did` files contain method-level documentation, but +drop documentation for: + +* type definitions +* record fields +* variant members + +This is problematic because much of the semantic meaning of a canister +interface is expressed on the data model rather than on the methods alone. +Request and response types often carry the important descriptions, constraints, +and invariants that downstream users need to understand. + +The goal of this proposal is to preserve Rust doc comments on exported Candid +types and their members so that generated `.did` files can act as a more +complete interface document. + +## Design + +The implementation follows the same overall pattern that is already used for +service method docs: doc comments are collected from Rust source and carried to +the textual Candid printer through a side channel. + +The crucial design choice is that docs are **not** stored on the structural type +representation itself. The runtime types in `candid::types` are used for +normalization, equality, hashing, and name assignment. Making docs part of +those structures would either perturb structural equality or require special +rules to ignore docs during comparison. Both would be awkward and fragile. + +Instead, the implementation introduces a parallel metadata flow: + +1. `candid_derive` extracts Rust doc comments while deriving `CandidType`. +2. `candid::types` memoizes that metadata per Rust `TypeId`. +3. `TypeContainer` translates that metadata to the final exported Candid type + names when constructing a `TypeEnv`. +4. `candid::pretty::candid` renders the docs when pretty-printing named type + definitions and their members. + +This preserves the current type pipeline while allowing documentation to follow +the exported interface. + +### Capturing docs in Rust derives + +When deriving `CandidType`, the derive machinery now extracts docs from: + +* the struct or enum item itself +* struct fields +* enum variants +* fields nested inside inline record payloads of variants + +The same extraction logic is shared with `#[candid_method]`, so line order and +blank lines are preserved consistently. + +The derive implementation emits both the existing structural type description +and a second metadata description. Concretely, the `CandidType` trait gains a +new default hook: + +```rust +fn _ty_doc() -> internal::TypeDoc { + internal::TypeDoc::default() +} +``` + +Manual implementations of `CandidType` remain valid because the new method has +a default implementation. + +### Representing type docs + +The additional metadata is represented separately from `TypeInner`: + +```rust +pub struct TypeDocs { + pub named: BTreeMap, +} + +pub struct TypeDoc { + pub docs: Vec, + pub fields: BTreeMap, +} + +pub struct FieldDoc { + pub docs: Vec, + pub ty: Option>, +} +``` + +`TypeDoc.docs` stores doc lines for a named type definition. + +`TypeDoc.fields` stores docs for members of that type. The key is the canonical +field identity used by Candid: + +* for named labels, `idl_hash(label)` +* for tuple labels, the numeric field id + +`FieldDoc.docs` stores the docs attached to a record field or variant member. + +`FieldDoc.ty` stores docs for nested inline record or variant payloads when +those payloads belong to a named exported type. + +### Type naming and association + +One challenge is that Rust names are not the final names used in textual +Candid. `TypeContainer` already rewrites the type graph to choose stable export +names and to normalize recursive and generic types. Therefore, doc attachment is +resolved at the same stage. + +The metadata is first memoized per Rust `TypeId`. When `TypeContainer` decides +to emit a named Candid type in `TypeEnv`, it also stores the corresponding doc +metadata under the exported type name. + +This ensures that doc comments remain attached correctly even when: + +* fields are sorted canonically by field id +* fields or variants are renamed through `serde(rename = "...")` +* generic or recursive types receive rewritten export names such as `List_1` + +### Rendering docs in textual Candid + +The existing `DocComments` structure currently stores only service method docs. +It is extended to also carry docs for named type definitions. + +When pretty-printing textual Candid, docs are emitted: + +* immediately above `type Foo = ...;` +* immediately above record fields +* immediately above variant members +* immediately above fields nested in inline record or variant payloads that are + rooted under a named type definition + +The existing service method behavior remains unchanged. + +The result is that Rust definitions such as + +```rust +/// Arguments for creating a cashier. +#[derive(CandidType, Deserialize)] +pub struct CashierArgs { + /// Human-readable display name. + pub name: String, + + /// Whether this cashier starts enabled. + pub enabled: bool, +} +``` + +produce Candid of the form + +```did +// Arguments for creating a cashier. +type CashierArgs = record { + // Whether this cashier starts enabled. + enabled : bool; + // Human-readable display name. + name : text; +}; +``` + +where field order still follows the canonical Candid ordering rules. + +## Special Cases + +### Tuples + +Tuple records are normally printed using tuple shorthand, e.g. + +```did +record { nat; text } +``` + +This syntax has no place to attach docs to individual tuple positions. Therefore +the printer falls back to explicit numeric fields when tuple members carry docs: + +```did +record { + // First element. + 0 : nat; + // Second element. + 1 : text; +} +``` + +This preserves the docs without changing the meaning of the Candid type. + +### Newtypes + +Single-field tuple structs continue to use their existing structural encoding. +Docs on the outer named type are preserved, but docs on the inner field are not +generally representable without changing the generated Candid shape. This +proposal deliberately preserves the existing shape. + +### Anonymous inline types in service signatures + +This proposal focuses on docs rooted at named exported types. Anonymous inline +record or variant types that appear only directly in service method arguments or +results are not given a separate documentation channel here. Supporting those +would require a second namespace keyed by service methods and argument/result +positions. + +## Alternatives + +### Docs on `TypeInner` + +One alternative is to store docs directly on `TypeInner` and `Field`, and let +the pretty-printer read them from the type tree itself. + +This was rejected because those structures are used for structural identity. +Adding docs there would either make otherwise-equal types compare differently, +or require custom equality semantics that ignore docs. Both options would make +the type system more brittle for the sake of a feature that only matters during +export. + +### Key docs directly in the pretty-printer + +Another option is to keep the core type system unchanged and let +`candid_derive` populate a richer `DocComments` map directly. + +This was rejected because `candid_derive` does not know the final exported +Candid names. If docs were keyed too early, they could become detached when the +type graph is normalized, fields are sorted, or export names are rewritten. + +### Rebuild a syntax AST + +The `candid_parser` crate already has a syntax-level pretty-printer that stores +docs directly on AST nodes. Reusing that machinery by reconstructing an AST from +the runtime type graph was considered as well. + +This was rejected as too invasive. It would require a larger refactoring of the +printer pipeline and still would not eliminate the need to transport doc +metadata from Rust derives into that AST. + +## Properties + +This design preserves the following properties: + +* structural type identity is unchanged +* service method docs continue to work as before +* doc extraction preserves line order and blank lines +* doc attachment is stable under canonical field sorting +* doc attachment is stable under renamed fields and variants +* generated `.did` output is deterministic + +## Tests + +The expected behavior is verified with end-to-end tests in +`rust/candid/tests/types.rs`. + +The tests cover: + +* docs on named record types +* docs on record fields +* docs on variant types +* docs on variant members +* coexistence with service method docs +* parsing the generated `.did` back through `candid_parser` and verifying that + docs are attached to the correct AST nodes + +These tests complement the existing parser-side doc comment tests in +`rust/candid_parser/tests`, which already cover syntax-level parsing and +pretty-printing of Candid comments. From 73191720ac9f5187bd517bf08eaf800ac019a5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 18:00:45 +0100 Subject: [PATCH 16/43] perf: fast-path small candid numbers (#709) **Overview** Performance improvement **Requirements** Preserve existing wire compatibility and keep record decoding behavior unchanged. **Solution** Encode and decode Nat and Int values through native LEB128 when they fit in machine integers so common small numbers avoid bigint work. **Considerations** I expect performance improvement and full forward and backward compatibility Ref. #710 --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- rust/candid/src/types/number.rs | 118 ++++++++++++++++++--- rust/candid/tests/compatibility_numbers.rs | 72 +++++++++++++ 2 files changed, 173 insertions(+), 17 deletions(-) create mode 100644 rust/candid/tests/compatibility_numbers.rs diff --git a/rust/candid/src/types/number.rs b/rust/candid/src/types/number.rs index a92b4744a..9f223c9ad 100644 --- a/rust/candid/src/types/number.rs +++ b/rust/candid/src/types/number.rs @@ -208,12 +208,29 @@ impl<'de> Deserialize<'de> for Nat { // LEB128 encoding for bignum. +/// Returns true if OR-ing `low_bits` at position `shift` fits within a u64. +/// The `shift < 64` guard prevents a debug-mode panic from the shift expression. +#[inline] +fn leb128_fits_u64(low_bits: u64, shift: u32) -> bool { + if shift == 0 { + true + } else if shift < 64 { + low_bits < (1u64 << (64 - shift)) + } else { + false + } +} + impl Nat { pub fn encode(&self, w: &mut W) -> crate::Result<()> where W: ?Sized + io::Write, { use num_traits::cast::ToPrimitive; + if let Some(value) = self.0.to_u64() { + leb128::write::unsigned(w, value)?; + return Ok(()); + } let zero = BigUint::from(0u8); let mut value = self.0.clone(); loop { @@ -234,17 +251,39 @@ impl Nat { where R: io::Read, { - let mut result = BigUint::from(0u8); - let mut shift = 0; + let mut small = 0u64; + let mut shift = 0u32; loop { let mut buf = [0]; r.read_exact(&mut buf)?; - let low_bits = BigUint::from(buf[0] & 0x7fu8); - result |= low_bits << shift; - if buf[0] & 0x80u8 == 0 { + let byte = buf[0]; + let low_bits = u64::from(byte & 0x7f); + if leb128_fits_u64(low_bits, shift) { + small |= low_bits << shift; + if byte & 0x80u8 == 0 { + return Ok(Nat(BigUint::from(small))); + } + shift += 7; + continue; + } + + let mut result = BigUint::from(small); + result |= BigUint::from(low_bits) << shift; + if byte & 0x80u8 == 0 { return Ok(Nat(result)); } shift += 7; + loop { + let mut buf = [0]; + r.read_exact(&mut buf)?; + let byte = buf[0]; + let low_bits = BigUint::from(byte & 0x7fu8); + result |= low_bits << shift; + if byte & 0x80u8 == 0 { + return Ok(Nat(result)); + } + shift += 7; + } } } } @@ -255,6 +294,10 @@ impl Int { W: ?Sized + io::Write, { use num_traits::cast::ToPrimitive; + if let Some(value) = self.0.to_i64() { + leb128::write::signed(w, value)?; + return Ok(()); + } let zero = BigInt::from(0); let mut value = self.0.clone(); loop { @@ -279,24 +322,65 @@ impl Int { where R: io::Read, { - let mut result = BigInt::from(0); - let mut shift = 0; - let mut byte; + let mut small = 0u64; + let mut shift = 0u32; loop { let mut buf = [0]; r.read_exact(&mut buf)?; - byte = buf[0]; - let low_bits = BigInt::from(byte & 0x7fu8); - result |= low_bits << shift; - shift += 7; + let byte = buf[0]; + let low_bits = u64::from(byte & 0x7f); + if leb128_fits_u64(low_bits, shift) { + small |= low_bits << shift; + if byte & 0x80u8 == 0 { + // Last byte: sign-extend and return as i64 if possible. + let total_bits = shift + 7; + if byte & 0x40u8 != 0 { + // Negative: fill bits from total_bits upward with 1s. + let signed = if total_bits >= 64 { + // Bit 63 is already the LEB sign bit inside small. + small as i64 + } else { + let sign_mask = !((1u64 << total_bits) - 1); + (small | sign_mask) as i64 + }; + return Ok(Int(BigInt::from(signed))); + } else if small <= i64::MAX as u64 { + return Ok(Int(BigInt::from(small as i64))); + } else { + // Positive value in [2^63, 2^64-1]: fits u64 but not i64. + return Ok(Int(BigInt::from(BigUint::from(small)))); + } + } + shift += 7; + continue; + } + + // Value overflows u64: fall back to BigInt for remaining bytes. + let mut result = BigInt::from(BigUint::from(small)); + result |= BigInt::from(low_bits) << shift; if byte & 0x80u8 == 0 { - break; + shift += 7; + if byte & 0x40u8 != 0 { + result |= BigInt::from(-1) << shift; + } + return Ok(Int(result)); + } + shift += 7; + loop { + let mut buf = [0]; + r.read_exact(&mut buf)?; + let byte = buf[0]; + let low_bits = BigInt::from(byte & 0x7fu8); + result |= low_bits << shift; + shift += 7; + if byte & 0x80u8 == 0 { + if byte & 0x40u8 != 0 { + result |= BigInt::from(-1) << shift; + } + return Ok(Int(result)); + } } } - if (0x40u8 & byte) == 0x40u8 { - result |= BigInt::from(-1) << shift; - } - Ok(Int(result)) } } diff --git a/rust/candid/tests/compatibility_numbers.rs b/rust/candid/tests/compatibility_numbers.rs new file mode 100644 index 000000000..025123c5c --- /dev/null +++ b/rust/candid/tests/compatibility_numbers.rs @@ -0,0 +1,72 @@ +use candid::{Decode, Encode, Int, Nat}; + +#[test] +fn number_fast_paths_preserve_small_and_large_values() { + let nat_values = vec![ + Nat::from(0u8), + Nat::from(42u64), + Nat::from(u64::MAX), + Nat::parse(b"24197857200151251861972493965091130863").unwrap(), + ]; + let nat_bytes = Encode!(&nat_values).unwrap(); + let decoded_nat_values = Decode!(&nat_bytes, Vec).unwrap(); + assert_eq!(decoded_nat_values, nat_values); + + let int_values = vec![ + Int::from(-1), + Int::from(42), + Int::from(i64::MIN), + Int::parse(b"-600000000000000000000000000000000000").unwrap(), + ]; + let int_bytes = Encode!(&int_values).unwrap(); + let decoded_int_values = Decode!(&int_bytes, Vec).unwrap(); + assert_eq!(decoded_int_values, int_values); +} + +/// Test the exact fast-path/BigInt transition boundaries using raw encode/decode. +#[test] +fn nat_decode_fast_path_boundaries() { + fn roundtrip(n: Nat) -> Nat { + let mut buf = Vec::new(); + n.encode(&mut buf).unwrap(); + Nat::decode(&mut buf.as_slice()).unwrap() + } + + // Last value handled entirely by the u64 fast path. + assert_eq!(roundtrip(Nat::from(u64::MAX)), Nat::from(u64::MAX)); + + // First value that overflows u64: must fall back to BigInt. + // u64::MAX + 1 = 18446744073709551616 + let just_over = Nat::parse(b"18446744073709551616").unwrap(); + assert_eq!(roundtrip(just_over.clone()), just_over); + + // A few values straddling the boundary. + for delta in 0u64..=8 { + let base = Nat::from(u64::MAX) - Nat::from(delta); + assert_eq!(roundtrip(base.clone()), base); + } +} + +/// Test the exact fast-path/BigInt transition boundaries for signed LEB128. +#[test] +fn int_decode_fast_path_boundaries() { + fn roundtrip(n: Int) -> Int { + let mut buf = Vec::new(); + n.encode(&mut buf).unwrap(); + Int::decode(&mut buf.as_slice()).unwrap() + } + + // Largest positive i64: handled by fast path. + assert_eq!(roundtrip(Int::from(i64::MAX)), Int::from(i64::MAX)); + + // 2^63 = i64::MAX + 1: fits u64 but not i64 (positive BigInt branch). + let just_over_pos = Int::parse(b"9223372036854775808").unwrap(); + assert_eq!(roundtrip(just_over_pos.clone()), just_over_pos); + + // Most negative i64: fast path with sign extension. + assert_eq!(roundtrip(Int::from(i64::MIN)), Int::from(i64::MIN)); + + // i64::MIN - 1: must fall back to BigInt negative path. + let just_under_neg = Int::parse(b"-9223372036854775809").unwrap(); + assert_eq!(roundtrip(just_under_neg.clone()), just_under_neg); +} From 3149c13d446c0d884e36d2522a5d962959c7803a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 18:03:27 +0100 Subject: [PATCH 17/43] perf: avoid cloning record fields during decode (#711) **Overview** Reduce per-record allocation churn during Candid record decoding. **Requirements** Preserve existing wire compatibility and keep record decoding behavior unchanged. **Solution** Track expected and wire record fields by type plus index instead of cloning field queues for each decoded record. Add a compatibility test covering backward and forward compatible record vectors. **Considerations** This keeps the wire format unchanged and is intended to remain fully compatible with existing Candid data. Series-level benchmark context is tracked in #710. --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- rust/candid/src/de.rs | 132 +++++++++++++++------ rust/candid/tests/compatibility_records.rs | 52 ++++++++ 2 files changed, 150 insertions(+), 34 deletions(-) create mode 100644 rust/candid/tests/compatibility_records.rs diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index a613bd553..28c32fcd7 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -851,9 +851,9 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { let len = Len::read(&mut self.input)?.0; visitor.visit_seq(Compound::new(self, Style::Vector { len, expect, wire })) } - (TypeInner::Record(e), TypeInner::Record(w)) => { - let expect = e.clone().into(); - let wire = w.clone().into(); + (TypeInner::Record(_), TypeInner::Record(_)) => { + let expect = self.expect_type.clone(); + let wire = self.wire_type.clone(); check!(self.expect_type.is_tuple(), "seq_tuple"); if !self.wire_type.is_tuple() { return Err(Error::subtype(format!( @@ -861,8 +861,15 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { self.wire_type ))); } - let value = - visitor.visit_seq(Compound::new(self, Style::Struct { expect, wire }))?; + let value = visitor.visit_seq(Compound::new( + self, + Style::Struct { + expect, + wire, + expect_idx: 0, + wire_idx: 0, + }, + ))?; Ok(value) } _ => check!(false), @@ -966,11 +973,16 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { self.unroll_type()?; self.add_cost(1)?; match (self.expect_type.as_ref(), self.wire_type.as_ref()) { - (TypeInner::Record(e), TypeInner::Record(w)) => { - let expect = e.clone().into(); - let wire = w.clone().into(); - let value = - visitor.visit_map(Compound::new(self, Style::Struct { expect, wire }))?; + (TypeInner::Record(_), TypeInner::Record(_)) => { + let value = visitor.visit_map(Compound::new( + self, + Style::Struct { + expect: self.expect_type.clone(), + wire: self.wire_type.clone(), + expect_idx: 0, + wire_idx: 0, + }, + ))?; Ok(value) } _ => check!(false), @@ -1041,8 +1053,10 @@ enum Style { wire: Type, }, Struct { - expect: VecDeque, - wire: VecDeque, + expect: Type, + wire: Type, + expect_idx: usize, + wire_idx: usize, }, Enum { expect: Field, @@ -1060,6 +1074,26 @@ struct Compound<'a, 'de> { style: Style, } +impl Style { + fn struct_remaining(&self) -> Option { + match self { + Style::Struct { + expect, + wire, + expect_idx, + wire_idx, + } => { + let remaining = |ty: &Type, idx: usize| match ty.as_ref() { + TypeInner::Record(fields) => fields.len().saturating_sub(idx), + _ => 0, + }; + Some(remaining(expect, *expect_idx).min(remaining(wire, *wire_idx))) + } + _ => None, + } + } +} + impl<'a, 'de> Compound<'a, 'de> { fn new(de: &'a mut Deserializer<'de>, style: Style) -> Self { Compound { de, style } @@ -1089,19 +1123,35 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { seed.deserialize(&mut *self.de).map(Some) } Style::Struct { - ref mut expect, - ref mut wire, + ref expect, + ref wire, + ref mut expect_idx, + ref mut wire_idx, } => { - if expect.is_empty() && wire.is_empty() { + let expect_fields = match expect.as_ref() { + TypeInner::Record(fields) => fields, + _ => unreachable!(), + }; + let wire_fields = match wire.as_ref() { + TypeInner::Record(fields) => fields, + _ => unreachable!(), + }; + if *expect_idx >= expect_fields.len() && *wire_idx >= wire_fields.len() { return Ok(None); } - self.de.expect_type = expect - .pop_front() - .map(|f| f.ty) + self.de.expect_type = expect_fields + .get(*expect_idx) + .map(|f| { + *expect_idx += 1; + f.ty.clone() + }) .unwrap_or_else(|| TypeInner::Reserved.into()); - self.de.wire_type = wire - .pop_front() - .map(|f| f.ty) + self.de.wire_type = wire_fields + .get(*wire_idx) + .map(|f| { + *wire_idx += 1; + f.ty.clone() + }) .unwrap_or_else(|| TypeInner::Null.into()); seed.deserialize(&mut *self.de).map(Some) } @@ -1112,8 +1162,7 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { fn size_hint(&self) -> Option { match &self.style { Style::Vector { len, .. } => Some(*len), - Style::Struct { expect, wire, .. } => Some(expect.len().min(wire.len())), - _ => None, + _ => self.style.struct_remaining(), } } } @@ -1127,23 +1176,36 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { self.de.add_cost(4)?; match self.style { Style::Struct { - ref mut expect, - ref mut wire, + ref expect, + ref wire, + ref mut expect_idx, + ref mut wire_idx, } => { - match (expect.front(), wire.front()) { + let expect_fields = match expect.as_ref() { + TypeInner::Record(fields) => fields, + _ => unreachable!(), + }; + let wire_fields = match wire.as_ref() { + TypeInner::Record(fields) => fields, + _ => unreachable!(), + }; + match (expect_fields.get(*expect_idx), wire_fields.get(*wire_idx)) { (Some(e), Some(w)) => { use std::cmp::Ordering; match e.id.get_id().cmp(&w.id.get_id()) { Ordering::Equal => { self.de.set_field_name(e.id.clone()); - self.de.expect_type = expect.pop_front().unwrap().ty; - self.de.wire_type = wire.pop_front().unwrap().ty; + self.de.expect_type = e.ty.clone(); + self.de.wire_type = w.ty.clone(); + *expect_idx += 1; + *wire_idx += 1; } Ordering::Less => { // by subtyping rules, expect_type can only be opt, reserved or null. let field = e.id.clone(); self.de.set_field_name(field.clone()); - let expect = expect.pop_front().unwrap().ty; + let expect = e.ty.clone(); + *expect_idx += 1; self.de.expect_type = self .de .table @@ -1159,20 +1221,23 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { } Ordering::Greater => { self.de.set_field_name(Label::Named("_".to_owned()).into()); - self.de.wire_type = wire.pop_front().unwrap().ty; + self.de.wire_type = w.ty.clone(); self.de.expect_type = TypeInner::Reserved.into(); + *wire_idx += 1; } } } (None, Some(_)) => { self.de.set_field_name(Label::Named("_".to_owned()).into()); - self.de.wire_type = wire.pop_front().unwrap().ty; + self.de.wire_type = wire_fields[*wire_idx].ty.clone(); self.de.expect_type = TypeInner::Reserved.into(); + *wire_idx += 1; } (Some(e), None) => { self.de.set_field_name(e.id.clone()); - self.de.expect_type = expect.pop_front().unwrap().ty; + self.de.expect_type = e.ty.clone(); self.de.wire_type = TypeInner::Null.into(); + *expect_idx += 1; } (None, None) => return Ok(None), } @@ -1216,8 +1281,7 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { fn size_hint(&self) -> Option { match &self.style { Style::Map { len, .. } => Some(*len), - Style::Struct { expect, wire, .. } => Some(expect.len().min(wire.len())), - _ => None, + _ => self.style.struct_remaining(), } } } diff --git a/rust/candid/tests/compatibility_records.rs b/rust/candid/tests/compatibility_records.rs new file mode 100644 index 000000000..e67fdc2c6 --- /dev/null +++ b/rust/candid/tests/compatibility_records.rs @@ -0,0 +1,52 @@ +use candid::{CandidType, Decode, Deserialize, Encode}; + +#[test] +fn record_vectors_remain_backward_and_forward_compatible() { + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, CandidType)] + struct Old { + id: u32, + flag: bool, + } + + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, CandidType)] + struct New { + extra: Option, + id: u32, + flag: bool, + } + + let old_values = vec![Old { id: 1, flag: true }, Old { id: 2, flag: false }]; + let old_bytes = Encode!(&old_values).unwrap(); + let decoded_new_values = Decode!(&old_bytes, Vec).unwrap(); + assert_eq!( + decoded_new_values, + vec![ + New { + extra: None, + id: 1, + flag: true, + }, + New { + extra: None, + id: 2, + flag: false, + }, + ] + ); + + let new_values = vec![ + New { + extra: Some(10), + id: 1, + flag: true, + }, + New { + extra: None, + id: 2, + flag: false, + }, + ]; + let new_bytes = Encode!(&new_values).unwrap(); + let decoded_old_values = Decode!(&new_bytes, Vec).unwrap(); + assert_eq!(decoded_old_values, old_values); +} From d9fd40c946ad21319a4a6e8b08a9355ee0c548bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 18:13:35 +0100 Subject: [PATCH 18/43] perf: fast-path exact primitive vector decode (#712) **Overview** Reduce repeated per-element type work when decoding primitive vectors. **Requirements** Preserve vector decoding semantics, including compatibility with extra trailing arguments. **Solution** Add an exact-primitive fast path for vector elements so deserialization can skip repeated type unrolling and checks when expected and wire element types already match. Add a compatibility test covering extra-args behavior. **Considerations** The optimization is limited to exact primitive matches and leaves the general decode path unchanged. Series-level benchmark context is tracked in #710. --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- rust/candid/src/de.rs | 156 ++++++++++++++++++--- rust/candid/tests/compatibility_vectors.rs | 50 +++++++ 2 files changed, 188 insertions(+), 18 deletions(-) create mode 100644 rust/candid/tests/compatibility_vectors.rs diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index 28c32fcd7..1dc9a19ae 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -302,6 +302,7 @@ struct Deserializer<'de> { is_untyped: bool, config: DecoderConfig, recursion_depth: crate::utils::RecursionDepth, + primitive_vec_fast_path: Option, } impl<'de> Deserializer<'de> { @@ -320,6 +321,7 @@ impl<'de> Deserializer<'de> { is_untyped: false, config: config.clone(), recursion_depth: crate::utils::RecursionDepth::new(), + primitive_vec_fast_path: None, }) } fn dump_state(&self) -> String { @@ -616,11 +618,48 @@ impl<'de> Deserializer<'de> { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum PrimitiveType { + Bool, + Int8, + Int16, + Int32, + Int64, + Nat8, + Nat16, + Nat32, + Nat64, + Float32, + Float64, +} + +fn exact_primitive_type(expect: &Type, wire: &Type) -> Option { + match (expect.as_ref(), wire.as_ref()) { + (TypeInner::Bool, TypeInner::Bool) => Some(PrimitiveType::Bool), + (TypeInner::Int8, TypeInner::Int8) => Some(PrimitiveType::Int8), + (TypeInner::Int16, TypeInner::Int16) => Some(PrimitiveType::Int16), + (TypeInner::Int32, TypeInner::Int32) => Some(PrimitiveType::Int32), + (TypeInner::Int64, TypeInner::Int64) => Some(PrimitiveType::Int64), + (TypeInner::Nat8, TypeInner::Nat8) => Some(PrimitiveType::Nat8), + (TypeInner::Nat16, TypeInner::Nat16) => Some(PrimitiveType::Nat16), + (TypeInner::Nat32, TypeInner::Nat32) => Some(PrimitiveType::Nat32), + (TypeInner::Nat64, TypeInner::Nat64) => Some(PrimitiveType::Nat64), + (TypeInner::Float32, TypeInner::Float32) => Some(PrimitiveType::Float32), + (TypeInner::Float64, TypeInner::Float64) => Some(PrimitiveType::Float64), + _ => None, + } +} + macro_rules! primitive_impl { - ($ty:ident, $type:expr, $cost:literal, $($value:tt)*) => { + ($ty:ident, $type:expr, $fast:expr, $cost:literal, $($value:tt)*) => { paste::item! { fn [](self, visitor: V) -> Result where V: Visitor<'de> { + if self.primitive_vec_fast_path == Some($fast) { + self.add_cost($cost)?; + let val = self.input.$($value)*().map_err(|_| Error::msg(format!("Cannot read {} value", stringify!($type))))?; + return visitor.[](val); + } self.unroll_type()?; check!(*self.expect_type == $type && *self.wire_type == $type, stringify!($type)); self.add_cost($cost)?; @@ -697,16 +736,64 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { v } - primitive_impl!(i8, TypeInner::Int8, 1, read_i8); - primitive_impl!(i16, TypeInner::Int16, 2, read_i16::); - primitive_impl!(i32, TypeInner::Int32, 4, read_i32::); - primitive_impl!(i64, TypeInner::Int64, 8, read_i64::); - primitive_impl!(u8, TypeInner::Nat8, 1, read_u8); - primitive_impl!(u16, TypeInner::Nat16, 2, read_u16::); - primitive_impl!(u32, TypeInner::Nat32, 4, read_u32::); - primitive_impl!(u64, TypeInner::Nat64, 8, read_u64::); - primitive_impl!(f32, TypeInner::Float32, 4, read_f32::); - primitive_impl!(f64, TypeInner::Float64, 8, read_f64::); + primitive_impl!(i8, TypeInner::Int8, PrimitiveType::Int8, 1, read_i8); + primitive_impl!( + i16, + TypeInner::Int16, + PrimitiveType::Int16, + 2, + read_i16:: + ); + primitive_impl!( + i32, + TypeInner::Int32, + PrimitiveType::Int32, + 4, + read_i32:: + ); + primitive_impl!( + i64, + TypeInner::Int64, + PrimitiveType::Int64, + 8, + read_i64:: + ); + primitive_impl!(u8, TypeInner::Nat8, PrimitiveType::Nat8, 1, read_u8); + primitive_impl!( + u16, + TypeInner::Nat16, + PrimitiveType::Nat16, + 2, + read_u16:: + ); + primitive_impl!( + u32, + TypeInner::Nat32, + PrimitiveType::Nat32, + 4, + read_u32:: + ); + primitive_impl!( + u64, + TypeInner::Nat64, + PrimitiveType::Nat64, + 8, + read_u64:: + ); + primitive_impl!( + f32, + TypeInner::Float32, + PrimitiveType::Float32, + 4, + read_f32:: + ); + primitive_impl!( + f64, + TypeInner::Float64, + PrimitiveType::Float64, + 8, + read_f64:: + ); fn is_human_readable(&self) -> bool { false @@ -752,10 +839,17 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { self.add_cost(1)?; visitor.visit_unit() } + // Bool is handled separately from `primitive_impl!` because its wire encoding + // uses `BoolValue::read` rather than a plain numeric read. fn deserialize_bool(self, visitor: V) -> Result where V: Visitor<'de>, { + if self.primitive_vec_fast_path == Some(PrimitiveType::Bool) { + self.add_cost(1)?; + let res = BoolValue::read(&mut self.input)?; + return visitor.visit_bool(res.0); + } self.unroll_type()?; check!( *self.expect_type == TypeInner::Bool && *self.wire_type == TypeInner::Bool, @@ -849,7 +943,16 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { let expect = e.clone(); let wire = self.table.trace_type_with_depth(w, &self.recursion_depth)?; let len = Len::read(&mut self.input)?.0; - visitor.visit_seq(Compound::new(self, Style::Vector { len, expect, wire })) + let exact_primitive = exact_primitive_type(&expect, &wire); + visitor.visit_seq(Compound::new( + self, + Style::Vector { + len, + expect, + wire, + exact_primitive, + }, + )) } (TypeInner::Record(_), TypeInner::Record(_)) => { let expect = self.expect_type.clone(); @@ -1051,6 +1154,7 @@ enum Style { len: usize, expect: Type, wire: Type, + exact_primitive: Option, }, Struct { expect: Type, @@ -1113,14 +1217,22 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { ref mut len, ref expect, ref wire, + exact_primitive, } => { if *len == 0 { return Ok(None); } *len -= 1; - self.de.expect_type = expect.clone(); - self.de.wire_type = wire.clone(); - seed.deserialize(&mut *self.de).map(Some) + let old_fast_path = self.de.primitive_vec_fast_path; + if let Some(exact_primitive) = exact_primitive { + self.de.primitive_vec_fast_path = Some(exact_primitive); + } else { + self.de.expect_type = expect.clone(); + self.de.wire_type = wire.clone(); + } + let result = seed.deserialize(&mut *self.de).map(Some); + self.de.primitive_vec_fast_path = old_fast_path; + result } Style::Struct { ref expect, @@ -1167,6 +1279,14 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { } } +impl Drop for Compound<'_, '_> { + fn drop(&mut self) { + // Reset fast-path state so it cannot leak if this Compound is dropped + // before all elements are consumed (e.g., on an error path). + self.de.primitive_vec_fast_path = None; + } +} + impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { type Error = Error; fn next_key_seed(&mut self, seed: K) -> Result> @@ -1337,7 +1457,7 @@ impl<'de> de::VariantAccess<'de> for Compound<'_, 'de> { T: de::DeserializeSeed<'de>, { self.de.add_cost(1)?; - seed.deserialize(self.de) + seed.deserialize(&mut *self.de) } fn tuple_variant(self, len: usize, visitor: V) -> Result @@ -1345,7 +1465,7 @@ impl<'de> de::VariantAccess<'de> for Compound<'_, 'de> { V: Visitor<'de>, { self.de.add_cost(1)?; - de::Deserializer::deserialize_tuple(self.de, len, visitor) + de::Deserializer::deserialize_tuple(&mut *self.de, len, visitor) } fn struct_variant(self, fields: &'static [&'static str], visitor: V) -> Result @@ -1353,6 +1473,6 @@ impl<'de> de::VariantAccess<'de> for Compound<'_, 'de> { V: Visitor<'de>, { self.de.add_cost(1)?; - de::Deserializer::deserialize_struct(self.de, "_", fields, visitor) + de::Deserializer::deserialize_struct(&mut *self.de, "_", fields, visitor) } } diff --git a/rust/candid/tests/compatibility_vectors.rs b/rust/candid/tests/compatibility_vectors.rs new file mode 100644 index 000000000..a1d42ba84 --- /dev/null +++ b/rust/candid/tests/compatibility_vectors.rs @@ -0,0 +1,50 @@ +use candid::{CandidType, Decode, Deserialize, Encode}; + +#[test] +fn primitive_vector_decode_stays_compatible_with_extra_args() { + let values = vec![-1i16, 0, 1, 42, i16::MIN, i16::MAX]; + let bytes = Encode!(&values, &123u8).unwrap(); + + let decoded_values = Decode!(&bytes, Vec).unwrap(); + assert_eq!(decoded_values, values); + + let (decoded_values, trailing) = Decode!(&bytes, Vec, u8).unwrap(); + assert_eq!(decoded_values, values); + assert_eq!(trailing, 123); +} + +#[test] +fn nested_primitive_vector_decode() { + // Outer vec is non-primitive so exact_primitive is None; inner vecs use the fast path. + let values: Vec> = vec![vec![1, 2], vec![], vec![3, i16::MIN, i16::MAX]]; + let bytes = Encode!(&values).unwrap(); + let decoded: Vec> = Decode!(&bytes, Vec>).unwrap(); + assert_eq!(decoded, values); +} + +#[test] +fn struct_with_primitive_vector_field() { + // Ensures primitive_vec_fast_path is correctly restored when a vec + // appears as a struct field alongside other fields. + #[derive(CandidType, Deserialize, PartialEq, Debug)] + struct S { + xs: Vec, + y: u8, + } + let s = S { + xs: vec![1, -1, i32::MAX], + y: 42, + }; + let bytes = Encode!(&s).unwrap(); + let decoded = Decode!(&bytes, S).unwrap(); + assert_eq!(decoded, s); +} + +#[test] +fn mismatched_rust_type_does_not_use_fast_path() { + // Wire type is vec nat16 but Rust target is Vec: expect and wire differ, + // so exact_primitive is None and the normal type-checking path rejects it. + let values: Vec = vec![1, 2, 3]; + let bytes = Encode!(&values).unwrap(); + assert!(Decode!(&bytes, Vec).is_err()); +} From 1cb9813a4ba4736b525e0b6534f91e5e2bdb8886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 18:16:17 +0100 Subject: [PATCH 19/43] perf: skip tokenizer trivia bookkeeping without trivia (#713) **Overview** Reduce parser work on tokenizer paths that do not capture trivia. **Requirements** Keep comment and doc-comment behavior unchanged when trivia capture is enabled. **Solution** Skip comment and line-tracking bookkeeping when the tokenizer runs without trivia capture. This trims successful DID and argument parsing on the hot path without changing the trivia-enabled path. **Considerations** Parser behavior with trivia capture is unchanged, and the parser test suite still passes. Series-level benchmark context is tracked in #710. Co-authored-by: Linwei Shang --- rust/candid_parser/src/token.rs | 35 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/rust/candid_parser/src/token.rs b/rust/candid_parser/src/token.rs index 37ea787fa..0281c31b0 100644 --- a/rust/candid_parser/src/token.rs +++ b/rust/candid_parser/src/token.rs @@ -246,12 +246,11 @@ impl Iterator for Tokenizer<'_> { Some(Err(LexicalError::new(err, span))) } Ok(Token::LineComment) => { - let content = parse_doc_comment(&self.lex); if self.trivia.is_some() && !self.is_inline_comment(span.start) { if self.has_blank_line_before_token(span.start) { self.comment_buffer.clear(); } - self.comment_buffer.push(content.to_string()); + self.comment_buffer.push(parse_doc_comment(&self.lex)); self.last_comment_end = Some(span.end); } self.next() @@ -278,9 +277,11 @@ impl Iterator for Tokenizer<'_> { } } self.lex = lex.morph::(); - // Update last_comment_end to skip over the block comment - // This prevents block comments from breaking doc comment continuity - self.last_comment_end = Some(self.lex.span().start); + if self.trivia.is_some() { + // Update last_comment_end to skip over the block comment. + // This prevents block comments from breaking doc comment continuity. + self.last_comment_end = Some(self.lex.span().start); + } self.next() } Ok(Token::StartString) => { @@ -359,21 +360,21 @@ impl Iterator for Tokenizer<'_> { Some(Ok((span.start, Token::Text(result), self.lex.span().end))) } Ok(token) => { - // Check for blank line before getting mutable reference - let has_blank_line = self.has_blank_line_before_token(span.start); - - if let Some(trivia) = &mut self.trivia { - if !self.comment_buffer.is_empty() { - if !has_blank_line { - let content: Vec = mem::take(&mut self.comment_buffer); - trivia.borrow_mut().insert(span.start, content); - } else { - self.comment_buffer.clear(); + if self.trivia.is_some() { + let has_blank_line = self.has_blank_line_before_token(span.start); + if let Some(trivia) = &mut self.trivia { + if !self.comment_buffer.is_empty() { + if !has_blank_line { + let content: Vec = mem::take(&mut self.comment_buffer); + trivia.borrow_mut().insert(span.start, content); + } else { + self.comment_buffer.clear(); + } } + self.last_comment_end = None; + self.last_token_line_end = Some(self.find_line_end(span.end)); } - self.last_comment_end = None; } - self.last_token_line_end = Some(self.find_line_end(span.end)); Some(Ok((span.start, token, span.end))) } From caf3fe4a4e427b6ff3e22b9d44fa88eeda3449f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 18:21:19 +0100 Subject: [PATCH 20/43] perf: avoid rebuilding parser types during validation (#714) **Overview** Reduce duplicate whole-file work during DID typechecking. **Requirements** Preserve parser validation semantics while improving large DID load performance. **Solution** Validate deferred parser semantics from the built type environment instead of rebuilding every declaration a second time in `check_decs`. **Considerations** This keeps the validation flow intact while removing redundant reconstruction work, and the parser test suite still passes. Series-level benchmark context is tracked in #710. Co-authored-by: Linwei Shang --- rust/candid_parser/src/typing.rs | 76 +++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/rust/candid_parser/src/typing.rs b/rust/candid_parser/src/typing.rs index f661dc8ea..5179a66fc 100644 --- a/rust/candid_parser/src/typing.rs +++ b/rust/candid_parser/src/typing.rs @@ -170,6 +170,80 @@ fn check_cycle(env: &TypeEnv) -> Result<()> { Ok(()) } +fn validate_func(env: &TypeEnv, seen: &mut BTreeMap, func: &Function) -> Result<()> { + for arg in func.args.iter() { + validate_type(env, seen, arg)?; + } + for ret in func.rets.iter() { + validate_type(env, seen, ret)?; + } + Ok(()) +} + +fn validate_type(env: &TypeEnv, seen: &mut BTreeMap, t: &Type) -> Result<()> { + match t.as_ref() { + TypeInner::Null + | TypeInner::Bool + | TypeInner::Nat + | TypeInner::Int + | TypeInner::Nat8 + | TypeInner::Nat16 + | TypeInner::Nat32 + | TypeInner::Nat64 + | TypeInner::Int8 + | TypeInner::Int16 + | TypeInner::Int32 + | TypeInner::Int64 + | TypeInner::Float32 + | TypeInner::Float64 + | TypeInner::Text + | TypeInner::Reserved + | TypeInner::Empty + | TypeInner::Unknown + | TypeInner::Principal + | TypeInner::Future + | TypeInner::Knot(_) => Ok(()), + TypeInner::Var(id) => match seen.get(id) { + Some(true) | Some(false) => Ok(()), + None => { + seen.insert(id.clone(), false); + let res = validate_type(env, seen, env.find_type(id)?); + seen.insert(id.clone(), true); + res + } + }, + TypeInner::Opt(ty) | TypeInner::Vec(ty) => validate_type(env, seen, ty), + TypeInner::Record(fields) | TypeInner::Variant(fields) => { + for field in fields.iter() { + validate_type(env, seen, &field.ty)?; + } + Ok(()) + } + TypeInner::Func(func) => validate_func(env, seen, func), + TypeInner::Service(methods) => { + for (_, ty) in methods.iter() { + let func = env.as_func(ty)?; + validate_func(env, seen, func)?; + } + Ok(()) + } + TypeInner::Class(args, ty) => { + for arg in args.iter() { + validate_type(env, seen, arg)?; + } + validate_type(env, seen, ty) + } + } +} + +fn validate_decs(env: &TypeEnv) -> Result<()> { + let mut seen = BTreeMap::new(); + for ty in env.0.values() { + validate_type(env, &mut seen, ty)?; + } + Ok(()) +} + fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { for dec in decs.iter() { if let Dec::TypD(Binding { id, .. }) = dec { @@ -183,7 +257,7 @@ fn check_decs(env: &mut Env, decs: &[Dec]) -> Result<()> { check_defs(env, decs)?; check_cycle(env.te)?; env.pre = false; - check_defs(env, decs)?; + validate_decs(env.te)?; Ok(()) } From eaf7e6c26caa428b46835dfe74c0b21777ff348b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 18:51:03 +0100 Subject: [PATCH 21/43] perf: bulk memcpy for primitive vector encoding on LE platforms (#715) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary On little-endian targets (including wasm32), `Vec` encoding now writes the backing memory directly instead of iterating element-by-element through the Serializer trait. - Added `try_write_raw_elements` default method to `Compound` trait - `ValueSerializer::Compound` implements it with direct byte write - `[T]::idl_serialize` detects fixed-width primitives via `TypeId` on `#[cfg(target_endian = "little")]` - `Vec` and `[T; N]` delegate to the optimized slice path - Excludes `usize`/`isize` (platform-dependent size) ## Benchmark (canbench, wasm32) | Metric | Before | After | Change | |--------|--------|-------|--------| | vec_int16 Encoding | 123.7M inst | 8.4M inst | **14.7x faster (-93.2%)** | | vec_int16 Total | 817.9M inst | 704.7M inst | -13.8% | No regressions on any of the 9 benchmarks. ## Compatibility Wire format is **unchanged** — produces byte-identical output. Round-trip compatibility test added. Relates to #710 --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- rust/candid/src/ser.rs | 4 ++ rust/candid/src/types/impls.rs | 46 +++++++++++++++++++--- rust/candid/src/types/mod.rs | 5 +++ rust/candid/tests/compatibility_vectors.rs | 28 +++++++++++++ 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/rust/candid/src/ser.rs b/rust/candid/src/ser.rs index 2ed78e7a9..6f7bdfdb2 100644 --- a/rust/candid/src/ser.rs +++ b/rust/candid/src/ser.rs @@ -230,6 +230,10 @@ impl types::Compound for Compound<'_> { use crate::types::Serializer; self.ser.serialize_blob(blob) } + fn try_write_raw_elements(&mut self, bytes: &[u8]) -> Result { + self.ser.write(bytes)?; + Ok(true) + } } /// A structure for serializing Rust values to IDL types. diff --git a/rust/candid/src/types/impls.rs b/rust/candid/src/types/impls.rs index 0682a4659..33092b760 100644 --- a/rust/candid/src/types/impls.rs +++ b/rust/candid/src/types/impls.rs @@ -122,6 +122,25 @@ where } } +#[cfg(target_endian = "little")] +fn fixed_primitive_byte_size() -> Option { + use super::internal::TypeId; + let tid = TypeId::of::(); + if tid == TypeId::of::() || tid == TypeId::of::() || tid == TypeId::of::() { + Some(1) + } else if tid == TypeId::of::() || tid == TypeId::of::() { + Some(2) + } else if tid == TypeId::of::() || tid == TypeId::of::() || tid == TypeId::of::() + { + Some(4) + } else if tid == TypeId::of::() || tid == TypeId::of::() || tid == TypeId::of::() + { + Some(8) + } else { + None + } +} + impl CandidType for [T] where T: CandidType, @@ -134,6 +153,15 @@ where S: Serializer, { let mut ser = serializer.serialize_vec(self.len())?; + #[cfg(target_endian = "little")] + if let Some(element_size) = fixed_primitive_byte_size::() { + let bytes = unsafe { + std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * element_size) + }; + if Compound::try_write_raw_elements(&mut ser, bytes)? { + return Ok(()); + } + } for e in self { Compound::serialize_element(&mut ser, &e)?; } @@ -242,7 +270,17 @@ use std::hash::{BuildHasher, Hash}; map_impl!(BTreeMap); map_impl!(HashMap); -seq_impl!(Vec); +impl CandidType for Vec { + fn _ty() -> Type { + TypeInner::Vec(K::ty()).into() + } + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: Serializer, + { + self.as_slice().idl_serialize(serializer) + } +} seq_impl!(VecDeque); seq_impl!(LinkedList); seq_impl!(BinaryHeap); @@ -257,11 +295,7 @@ impl CandidType for [T; N] { where S: Serializer, { - let mut ser = serializer.serialize_vec(N)?; - for e in self { - Compound::serialize_element(&mut ser, &e)?; - } - Ok(()) + self.as_slice().idl_serialize(serializer) } } diff --git a/rust/candid/src/types/mod.rs b/rust/candid/src/types/mod.rs index 42b905473..06a28daf8 100644 --- a/rust/candid/src/types/mod.rs +++ b/rust/candid/src/types/mod.rs @@ -108,4 +108,9 @@ pub trait Compound { // or generalize this function to take a closure for with. #[doc(hidden)] fn serialize_blob(&mut self, blob: &[u8]) -> Result<(), Self::Error>; + /// Write raw element bytes directly for a primitive vector. + /// Returns Ok(true) if written, Ok(false) if not supported (caller should use per-element fallback). + fn try_write_raw_elements(&mut self, _bytes: &[u8]) -> Result { + Ok(false) + } } diff --git a/rust/candid/tests/compatibility_vectors.rs b/rust/candid/tests/compatibility_vectors.rs index a1d42ba84..f33254fd0 100644 --- a/rust/candid/tests/compatibility_vectors.rs +++ b/rust/candid/tests/compatibility_vectors.rs @@ -48,3 +48,31 @@ fn mismatched_rust_type_does_not_use_fast_path() { let bytes = Encode!(&values).unwrap(); assert!(Decode!(&bytes, Vec).is_err()); } + +#[test] +fn bulk_encode_primitive_vectors_round_trip() { + let u8s: Vec = vec![0, 1, 127, 255]; + assert_eq!(Decode!(&Encode!(&u8s).unwrap(), Vec).unwrap(), u8s); + + let i16s: Vec = vec![i16::MIN, -1, 0, 1, i16::MAX]; + assert_eq!(Decode!(&Encode!(&i16s).unwrap(), Vec).unwrap(), i16s); + + let u32s: Vec = vec![0, 1, u32::MAX]; + assert_eq!(Decode!(&Encode!(&u32s).unwrap(), Vec).unwrap(), u32s); + + let f64s: Vec = vec![0.0, -1.0, f64::INFINITY, f64::NAN]; + let decoded = Decode!(&Encode!(&f64s).unwrap(), Vec).unwrap(); + assert_eq!(decoded[0], 0.0); + assert_eq!(decoded[1], -1.0); + assert_eq!(decoded[2], f64::INFINITY); + assert!(decoded[3].is_nan()); + + let bools: Vec = vec![true, false, true]; + assert_eq!( + Decode!(&Encode!(&bools).unwrap(), Vec).unwrap(), + bools + ); + + let empty: Vec = vec![]; + assert_eq!(Decode!(&Encode!(&empty).unwrap(), Vec).unwrap(), empty); +} From beb56d50ab2c864f8ddc7ab7eae3648d0fc0c0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 19:03:16 +0100 Subject: [PATCH 22/43] perf: batch cost tracking for primitive vector decode (#716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Pre-compute the total decoding cost for primitive vectors at the vector level instead of calling `add_cost` per element. Also sets `primitive_vec_fast_path` once before `visit_seq` rather than saving and restoring it on every element. - Added `primitive_byte_cost` helper - Pre-computed `len * (3 + byte_cost)` in `deserialize_seq` for primitive vectors - Removed per-element `add_cost` from `primitive_impl!` and `deserialize_bool` fast paths - Moved `add_cost(3)` into non-primitive branch in `next_element_seed` ## Benchmark (canbench, wasm32) | Metric | Before | After | Change | |--------|--------|-------|--------| | vec_int16 Decoding | 694.2M inst | 409.0M inst | **41.1% faster** | | vec_int16 Total | 817.9M inst | 532.7M inst | -34.9% | No regressions on any of the 9 benchmarks. ## Compatibility Wire format and cost accounting semantics are **unchanged**. The total cost charged is identical — it's just computed once upfront instead of incrementally. Relates to #710 --- rust/candid/src/de.rs | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index 1dc9a19ae..710c757bc 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -633,6 +633,15 @@ enum PrimitiveType { Float64, } +fn primitive_byte_cost(p: PrimitiveType) -> usize { + match p { + PrimitiveType::Bool | PrimitiveType::Int8 | PrimitiveType::Nat8 => 1, + PrimitiveType::Int16 | PrimitiveType::Nat16 => 2, + PrimitiveType::Int32 | PrimitiveType::Nat32 | PrimitiveType::Float32 => 4, + PrimitiveType::Int64 | PrimitiveType::Nat64 | PrimitiveType::Float64 => 8, + } +} + fn exact_primitive_type(expect: &Type, wire: &Type) -> Option { match (expect.as_ref(), wire.as_ref()) { (TypeInner::Bool, TypeInner::Bool) => Some(PrimitiveType::Bool), @@ -656,7 +665,6 @@ macro_rules! primitive_impl { fn [](self, visitor: V) -> Result where V: Visitor<'de> { if self.primitive_vec_fast_path == Some($fast) { - self.add_cost($cost)?; let val = self.input.$($value)*().map_err(|_| Error::msg(format!("Cannot read {} value", stringify!($type))))?; return visitor.[](val); } @@ -846,7 +854,6 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { V: Visitor<'de>, { if self.primitive_vec_fast_path == Some(PrimitiveType::Bool) { - self.add_cost(1)?; let res = BoolValue::read(&mut self.input)?; return visitor.visit_bool(res.0); } @@ -944,7 +951,15 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { let wire = self.table.trace_type_with_depth(w, &self.recursion_depth)?; let len = Len::read(&mut self.input)?.0; let exact_primitive = exact_primitive_type(&expect, &wire); - visitor.visit_seq(Compound::new( + if let Some(prim) = exact_primitive { + let per_element_cost = 3 + primitive_byte_cost(prim); + self.add_cost( + len.checked_mul(per_element_cost) + .ok_or_else(|| Error::msg("Vec length overflow"))?, + )?; + self.primitive_vec_fast_path = exact_primitive; + } + let result = visitor.visit_seq(Compound::new( self, Style::Vector { len, @@ -952,7 +967,11 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { wire, exact_primitive, }, - )) + )); + if exact_primitive.is_some() { + self.primitive_vec_fast_path = None; + } + result } (TypeInner::Record(_), TypeInner::Record(_)) => { let expect = self.expect_type.clone(); @@ -1211,7 +1230,6 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { where T: de::DeserializeSeed<'de>, { - self.de.add_cost(3)?; match self.style { Style::Vector { ref mut len, @@ -1223,16 +1241,14 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { return Ok(None); } *len -= 1; - let old_fast_path = self.de.primitive_vec_fast_path; - if let Some(exact_primitive) = exact_primitive { - self.de.primitive_vec_fast_path = Some(exact_primitive); + if exact_primitive.is_some() { + seed.deserialize(&mut *self.de).map(Some) } else { + self.de.add_cost(3)?; self.de.expect_type = expect.clone(); self.de.wire_type = wire.clone(); + seed.deserialize(&mut *self.de).map(Some) } - let result = seed.deserialize(&mut *self.de).map(Some); - self.de.primitive_vec_fast_path = old_fast_path; - result } Style::Struct { ref expect, @@ -1240,6 +1256,7 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { ref mut expect_idx, ref mut wire_idx, } => { + self.de.add_cost(3)?; let expect_fields = match expect.as_ref() { TypeInner::Record(fields) => fields, _ => unreachable!(), From 325b27547213b4ec5fb1aef7c7304be3169c86b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 19:11:37 +0100 Subject: [PATCH 23/43] perf: Int::decode small-value fast path (#717) ## Summary Decode SLEB128-encoded `Int` values into `i64` first, only falling back to `BigInt` for values that exceed 64-bit range. Mirrors the existing `Nat::decode` fast path from an earlier commit in this series. - Fast path: accumulates SLEB128 bytes into `i64` with proper sign extension - Fallback: converts accumulated `i64` to `BigInt` and continues with `BigInt` arithmetic - Most real-world `Int` values fit in `i64`, avoiding heap allocation entirely ## Benchmark (canbench, wasm32) | Metric | Before | After | Change | |--------|--------|-------|--------| | option_list Decoding | 26.2M inst | 23.2M inst | **-11.7%** | | variant_list Decoding | 25.1M inst | 22.1M inst | **-11.9%** | | option_list Heap | 2 pages | 1 page | **-50%** | | variant_list Heap | 2 pages | 1 page | **-50%** | No regressions on any of the 9 benchmarks. ## Compatibility Wire format is **unchanged**. Additional edge-case values added to the round-trip compatibility test (Int(0), Int(i64::MAX), large positive Int). Relates to #710 --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- rust/candid/src/types/number.rs | 80 +++++++++------------- rust/candid/tests/compatibility_numbers.rs | 3 + 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/rust/candid/src/types/number.rs b/rust/candid/src/types/number.rs index 9f223c9ad..14ba0b89a 100644 --- a/rust/candid/src/types/number.rs +++ b/rust/candid/src/types/number.rs @@ -208,19 +208,6 @@ impl<'de> Deserialize<'de> for Nat { // LEB128 encoding for bignum. -/// Returns true if OR-ing `low_bits` at position `shift` fits within a u64. -/// The `shift < 64` guard prevents a debug-mode panic from the shift expression. -#[inline] -fn leb128_fits_u64(low_bits: u64, shift: u32) -> bool { - if shift == 0 { - true - } else if shift < 64 { - low_bits < (1u64 << (64 - shift)) - } else { - false - } -} - impl Nat { pub fn encode(&self, w: &mut W) -> crate::Result<()> where @@ -258,7 +245,7 @@ impl Nat { r.read_exact(&mut buf)?; let byte = buf[0]; let low_bits = u64::from(byte & 0x7f); - if leb128_fits_u64(low_bits, shift) { + if shift == 0 || (shift < 64 && low_bits < (1u64 << (64 - shift))) { small |= low_bits << shift; if byte & 0x80u8 == 0 { return Ok(Nat(BigUint::from(small))); @@ -322,59 +309,58 @@ impl Int { where R: io::Read, { - let mut small = 0u64; + let mut small = 0i64; let mut shift = 0u32; loop { let mut buf = [0]; r.read_exact(&mut buf)?; let byte = buf[0]; - let low_bits = u64::from(byte & 0x7f); - if leb128_fits_u64(low_bits, shift) { + let low_bits = i64::from(byte & 0x7f); + + let fits_i64 = if shift < 57 { + true + } else if shift < 64 { + let remaining_bits = 64 - shift; + if (byte & 0x40) != 0 { + (low_bits | !0x7f) >> (remaining_bits - 1) == -1 + } else { + low_bits >> (remaining_bits - 1) == 0 + } + } else { + false + }; + + if fits_i64 { small |= low_bits << shift; - if byte & 0x80u8 == 0 { - // Last byte: sign-extend and return as i64 if possible. - let total_bits = shift + 7; - if byte & 0x40u8 != 0 { - // Negative: fill bits from total_bits upward with 1s. - let signed = if total_bits >= 64 { - // Bit 63 is already the LEB sign bit inside small. - small as i64 - } else { - let sign_mask = !((1u64 << total_bits) - 1); - (small | sign_mask) as i64 - }; - return Ok(Int(BigInt::from(signed))); - } else if small <= i64::MAX as u64 { - return Ok(Int(BigInt::from(small as i64))); - } else { - // Positive value in [2^63, 2^64-1]: fits u64 but not i64. - return Ok(Int(BigInt::from(BigUint::from(small)))); + shift += 7; + if byte & 0x80 == 0 { + if shift < 64 && (byte & 0x40) != 0 { + small |= !0i64 << shift; } + return Ok(Int(BigInt::from(small))); } - shift += 7; continue; } - // Value overflows u64: fall back to BigInt for remaining bytes. - let mut result = BigInt::from(BigUint::from(small)); - result |= BigInt::from(low_bits) << shift; - if byte & 0x80u8 == 0 { - shift += 7; - if byte & 0x40u8 != 0 { + let mut result = BigInt::from(small); + let big_low_bits = BigInt::from(byte & 0x7fu8); + result |= big_low_bits << shift; + shift += 7; + if byte & 0x80 == 0 { + if (byte & 0x40) != 0 { result |= BigInt::from(-1) << shift; } return Ok(Int(result)); } - shift += 7; loop { let mut buf = [0]; r.read_exact(&mut buf)?; let byte = buf[0]; - let low_bits = BigInt::from(byte & 0x7fu8); - result |= low_bits << shift; + let big_low_bits = BigInt::from(byte & 0x7fu8); + result |= big_low_bits << shift; shift += 7; - if byte & 0x80u8 == 0 { - if byte & 0x40u8 != 0 { + if byte & 0x80 == 0 { + if (byte & 0x40) != 0 { result |= BigInt::from(-1) << shift; } return Ok(Int(result)); diff --git a/rust/candid/tests/compatibility_numbers.rs b/rust/candid/tests/compatibility_numbers.rs index 025123c5c..c5f756a01 100644 --- a/rust/candid/tests/compatibility_numbers.rs +++ b/rust/candid/tests/compatibility_numbers.rs @@ -13,9 +13,12 @@ fn number_fast_paths_preserve_small_and_large_values() { assert_eq!(decoded_nat_values, nat_values); let int_values = vec![ + Int::from(0), Int::from(-1), Int::from(42), Int::from(i64::MIN), + Int::from(i64::MAX), + Int::parse(b"24197857200151251861972493965091130863").unwrap(), Int::parse(b"-600000000000000000000000000000000000").unwrap(), ]; let int_bytes = Encode!(&int_values).unwrap(); From 3759322f794e6897057b9d52547fe1cf2db8fa81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Sun, 15 Mar 2026 19:27:46 +0100 Subject: [PATCH 24/43] bench: add vec_nat, vec_nat32, vec_nat64 benchmarks (#718) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds three new canbench benchmarks to complement the existing `vec_int16`: - **`vec_nat`** — 262K `Nat` elements (variable-length LEB128 encoding). Exercises the `Nat::encode`/`decode` paths and the serde visitor round-trip for bignum types. - **`vec_nat32`** — 2M `u32` elements (fixed-width 4 bytes). Tests fixed-width primitive vector performance. - **`vec_nat64`** — 2M `u64` elements (fixed-width 8 bytes). Tests the largest fixed-width primitive vector. ### Baseline results (master, `ba72cf4`) | Benchmark | Encoding | Decoding | Total | Heap | |-----------|----------|----------|-------|------| | vec_nat | 1.10B | 1.49B | 2.59B | 172 pages | | vec_nat32 | 136.28M | 1.06B | 1.20B | 518 pages | | vec_nat64 | 161.45M | 1.06B | 1.22B | 1031 pages | These baselines make the impact of the performance work in `sat-perf-improvements` directly measurable: | Benchmark | Metric | Master | Optimized | Change | |-----------|--------|--------|-----------|--------| | **vec_nat** | Encoding | 1.10B | 66.54M | **-93.9% (16.5x)** | | **vec_nat** | Decoding | 1.49B | 917.08M | **-38.5%** | | **vec_nat** | Total | 2.59B | 983.62M | **-62.0%** | | **vec_nat32** | Encoding | 136.28M | 16.79M | **-87.7% (8.1x)** | | **vec_nat32** | Decoding | 1.06B | 406.87M | **-61.6%** | | **vec_nat32** | Total | 1.20B | 423.67M | **-64.7%** | | **vec_nat64** | Encoding | 161.45M | 33.57M | **-79.2% (4.8x)** | | **vec_nat64** | Decoding | 1.06B | 411.07M | **-61.2%** | | **vec_nat64** | Total | 1.22B | 444.64M | **-63.6%** | Relates to #710 --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- .gitignore | 1 + rust/bench/bench.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fa8bf2662..75e44429f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ target/ .canbench/ +canbench_results.yml # IDEs .idea/ diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 9ec60beed..770d8aac2 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -6,7 +6,7 @@ use std::collections::BTreeMap; mod nns; const N: usize = 2097152; -const COST: usize = 20_000_000; +const COST: usize = 25_000_000; const SKIP: usize = 10_000; #[bench(raw)] @@ -62,6 +62,57 @@ fn vec_int16() -> BenchResult { }) } +#[bench(raw)] +fn vec_nat() -> BenchResult { + let vec: Vec = (0u64..262144).map(Nat::from).collect(); + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&vec).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +#[bench(raw)] +fn vec_nat64() -> BenchResult { + let vec: Vec = (0u64..N as u64).collect(); + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&vec).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +#[bench(raw)] +fn vec_nat32() -> BenchResult { + let vec: Vec = (0u32..N as u32).collect(); + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&vec).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + #[bench(raw)] fn btreemap() -> BenchResult { let mut config = DecoderConfig::new(); From e0f393d2ffda0481aca6f92aa4ce2b9c0fd6e512 Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Mon, 16 Mar 2026 11:18:06 -0400 Subject: [PATCH 25/43] chore: Release candid v0.10.25 (#719) This release also includes some performance improvements that are not shown in the changelog. Co-authored-by: Claude Sonnet 4.6 --- CHANGELOG.md | 4 +++- Cargo.lock | 8 ++++---- rust/bench/Cargo.lock | 4 ++-- rust/candid/Cargo.toml | 4 ++-- rust/candid_derive/Cargo.toml | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81bdcd1ee..cb2fccf61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog -## Unreleased +## 2026-03-16 + +### Candid 0.10.25 * Non-breaking changes: + Preserve Rust doc comments on exported Candid types, record fields, and variant members when generating `.did` files via `#[derive(CandidType)]` diff --git a/Cargo.lock b/Cargo.lock index 319ab82af..47cac7723 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,13 +209,13 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.24" +version = "0.10.25" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.24", + "candid_derive 0.10.25", "candid_parser 0.3.0", "hex", "ic_principal 0.1.2", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.24" +version = "0.10.25" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -280,7 +280,7 @@ version = "0.3.0" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.24", + "candid 0.10.25", "codespan-reporting", "console", "convert_case", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index 2400c4bc3..0700075b4 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.24" +version = "0.10.25" dependencies = [ "anyhow", "binread", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.24" +version = "0.10.25" dependencies = [ "lazy_static", "proc-macro2", diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index afa6fb959..222b0409f 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.24" +version = "0.10.25" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.24" } +candid_derive = { path = "../candid_derive", version = "=0.10.25" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index 2e551bd51..473dc7299 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.24" +version = "0.10.25" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] From defcf8308d8552152240f7f25c007bd2bd4355bd Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Wed, 18 Mar 2026 10:03:12 -0400 Subject: [PATCH 26/43] fix: primitive vec fast-path skip bug + release 0.10.26 (#720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fix decoding failure when a trailing/extra argument is a primitive vector (`vec int8/16/32/64`, `vec nat8/16/32/64`, `vec float32/64`, `vec bool`) - The fast-path optimization (#712) skipped setting `expect_type`/`wire_type` per element; `deserialize_ignored_any` then misidentified the element type and corrupted the byte stream - Fix: always set element types before calling `seed.deserialize`, skipping only the `add_cost(3)` call in the fast path - Release candid 0.10.26 ## Test plan - [ ] New regression test `primitive_vector_is_extra_args` in `tests/compatibility_vectors.rs` covers the exact failure scenario - [ ] All existing `compatibility_vectors` tests continue to pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 --- CHANGELOG.md | 7 +++++++ Cargo.lock | 8 ++++---- rust/bench/Cargo.lock | 4 ++-- rust/candid/Cargo.toml | 4 ++-- rust/candid/src/de.rs | 10 ++++------ rust/candid/tests/compatibility_vectors.rs | 9 +++++++++ rust/candid_derive/Cargo.toml | 2 +- 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb2fccf61..2533fe9b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2026-03-18 + +### Candid 0.10.26 + +* Bug fixes: + + Fix decoding failure when a trailing argument is a primitive vector + ## 2026-03-16 ### Candid 0.10.25 diff --git a/Cargo.lock b/Cargo.lock index 47cac7723..8eb17f36e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,13 +209,13 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.25" +version = "0.10.26" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.25", + "candid_derive 0.10.26", "candid_parser 0.3.0", "hex", "ic_principal 0.1.2", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.25" +version = "0.10.26" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -280,7 +280,7 @@ version = "0.3.0" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.25", + "candid 0.10.26", "codespan-reporting", "console", "convert_case", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index 0700075b4..ef54d1e25 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.25" +version = "0.10.26" dependencies = [ "anyhow", "binread", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.25" +version = "0.10.26" dependencies = [ "lazy_static", "proc-macro2", diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index 222b0409f..5655ede03 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.25" +version = "0.10.26" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.25" } +candid_derive = { path = "../candid_derive", version = "=0.10.26" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index 710c757bc..341d9ae90 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -1241,14 +1241,12 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { return Ok(None); } *len -= 1; - if exact_primitive.is_some() { - seed.deserialize(&mut *self.de).map(Some) - } else { + self.de.expect_type = expect.clone(); + self.de.wire_type = wire.clone(); + if exact_primitive.is_none() { self.de.add_cost(3)?; - self.de.expect_type = expect.clone(); - self.de.wire_type = wire.clone(); - seed.deserialize(&mut *self.de).map(Some) } + seed.deserialize(&mut *self.de).map(Some) } Style::Struct { ref expect, diff --git a/rust/candid/tests/compatibility_vectors.rs b/rust/candid/tests/compatibility_vectors.rs index f33254fd0..b76e9904d 100644 --- a/rust/candid/tests/compatibility_vectors.rs +++ b/rust/candid/tests/compatibility_vectors.rs @@ -76,3 +76,12 @@ fn bulk_encode_primitive_vectors_round_trip() { let empty: Vec = vec![]; assert_eq!(Decode!(&Encode!(&empty).unwrap(), Vec).unwrap(), empty); } + +#[test] +fn primitive_vector_is_extra_args() { + let extra = vec![1i16, 2, 3]; + let bytes = Encode!(&123u8, &extra).unwrap(); + + let decoded_values = Decode!(&bytes, u8).unwrap(); + assert_eq!(decoded_values, 123); +} diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index 473dc7299..9aee2aa3f 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.25" +version = "0.10.26" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] From 99ef1fa1c999bf622fe40d4bd8560ab1017c28d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Wed, 18 Mar 2026 16:13:07 +0100 Subject: [PATCH 27/43] perf: optimize decode paths for Nat/Int, primitive vecs, and strings (#721) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Overview** Four decode-side optimizations, all behavior-preserving: 1. Nat/Int deserialization bypass: for values fitting u64/i64, read LEB128 directly and call visitor.visit_u64/i64, avoiding the BigUint/BigInt → bytes → BigUint round-trip (saves 3 allocations per value). 2. BigNum vector fast path: batch cost tracking and skip per-element type cloning/checking for Vec, Vec, and Vec with Nat wire type, mirroring the existing primitive vec fast path. 3. PrimitiveVecAccess with IntoDeserializer: on LE platforms, decode primitive vectors via a lightweight SeqAccess that reads directly from the input byte slice using serde's IntoDeserializer, bypassing the full Deserializer and Cursor overhead. 4. Borrowed string deserialization: use visit_borrowed_str instead of copying bytes, enabling zero-copy for &str targets. Benchmark improvements (decode, vs previous optimized baseline): vec_nat: 910M → 300M (-67%) vec_nat32: 406M → 247M (-39%) vec_nat64: 411M → 255M (-38%) vec_int16: 411M → 251M (-39%) btreemap: 13.3B → 11.2B (-16%) option_list: 23M → 18M (-20%) variant_list: 21M → 17M (-21%) --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- rust/candid/src/de.rs | 233 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 209 insertions(+), 24 deletions(-) diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index 341d9ae90..d84491f92 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -303,6 +303,8 @@ struct Deserializer<'de> { config: DecoderConfig, recursion_depth: crate::utils::RecursionDepth, primitive_vec_fast_path: Option, + #[cfg(feature = "bignum")] + bignum_vec_fast_path: Option, } impl<'de> Deserializer<'de> { @@ -322,6 +324,8 @@ impl<'de> Deserializer<'de> { config: config.clone(), recursion_depth: crate::utils::RecursionDepth::new(), primitive_vec_fast_path: None, + #[cfg(feature = "bignum")] + bignum_vec_fast_path: None, }) } fn dump_state(&self) -> String { @@ -442,10 +446,37 @@ impl<'de> Deserializer<'de> { where V: Visitor<'de>, { - self.unroll_type()?; - assert!(*self.expect_type == TypeInner::Int); - let mut bytes = vec![0u8]; + if self.bignum_vec_fast_path.is_none() { + self.unroll_type()?; + assert!(*self.expect_type == TypeInner::Int); + } let pos = self.input.position(); + if !self.is_untyped { + match self.wire_type.as_ref() { + TypeInner::Int => match leb128::read::signed(&mut self.input) { + Ok(value) => { + self.add_cost((self.input.position() - pos) as usize)?; + return visitor.visit_i64(value); + } + Err(leb128::read::Error::Overflow) => { + self.input.set_position(pos); + } + Err(e) => return Err(Error::msg(e)), + }, + TypeInner::Nat => match leb128::read::unsigned(&mut self.input) { + Ok(value) => { + self.add_cost((self.input.position() - pos) as usize)?; + return visitor.visit_u64(value); + } + Err(leb128::read::Error::Overflow) => { + self.input.set_position(pos); + } + Err(e) => return Err(Error::msg(e)), + }, + t => return Err(Error::subtype(format!("{t} cannot be deserialized to int"))), + } + } + let mut bytes = vec![0u8]; let int = match self.wire_type.as_ref() { TypeInner::Int => Int::decode(&mut self.input).map_err(Error::msg)?, TypeInner::Nat => Int(Nat::decode(&mut self.input).map_err(Error::msg)?.0.into()), @@ -461,13 +492,27 @@ impl<'de> Deserializer<'de> { where V: Visitor<'de>, { - self.unroll_type()?; - check!( - *self.expect_type == TypeInner::Nat && *self.wire_type == TypeInner::Nat, - "nat" - ); - let mut bytes = vec![1u8]; + if self.bignum_vec_fast_path.is_none() { + self.unroll_type()?; + check!( + *self.expect_type == TypeInner::Nat && *self.wire_type == TypeInner::Nat, + "nat" + ); + } let pos = self.input.position(); + if !self.is_untyped { + match leb128::read::unsigned(&mut self.input) { + Ok(value) => { + self.add_cost((self.input.position() - pos) as usize)?; + return visitor.visit_u64(value); + } + Err(leb128::read::Error::Overflow) => { + self.input.set_position(pos); + } + Err(e) => return Err(Error::msg(e)), + } + } + let mut bytes = vec![1u8]; let nat = Nat::decode(&mut self.input).map_err(Error::msg)?; self.add_cost((self.input.position() - pos) as usize)?; bytes.extend_from_slice(&nat.0.to_bytes_le()); @@ -642,6 +687,14 @@ fn primitive_byte_cost(p: PrimitiveType) -> usize { } } +#[cfg(feature = "bignum")] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum BigNumFastPath { + Nat, + Int, + NatAsInt, +} + fn exact_primitive_type(expect: &Type, wire: &Type) -> Option { match (expect.as_ref(), wire.as_ref()) { (TypeInner::Bool, TypeInner::Bool) => Some(PrimitiveType::Bool), @@ -687,6 +740,13 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { if self.field_name.is_some() { return self.deserialize_identifier(visitor); } + #[cfg(feature = "bignum")] + if let Some(fast) = self.bignum_vec_fast_path { + return match fast { + BigNumFastPath::Nat => self.deserialize_nat(visitor), + BigNumFastPath::Int | BigNumFastPath::NatAsInt => self.deserialize_int(visitor), + }; + } self.unroll_type()?; match self.expect_type.as_ref() { #[cfg(feature = "bignum")] @@ -870,16 +930,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - self.unroll_type()?; - check!( - *self.expect_type == TypeInner::Text && *self.wire_type == TypeInner::Text, - "text" - ); - let len = Len::read(&mut self.input)?.0; - self.add_cost(len.saturating_add(1))?; - let bytes = self.borrow_bytes(len)?.to_owned(); - let value = String::from_utf8(bytes).map_err(Error::msg)?; - visitor.visit_string(value) + self.deserialize_str(visitor) } fn deserialize_str(self, visitor: V) -> Result where @@ -957,7 +1008,61 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { len.checked_mul(per_element_cost) .ok_or_else(|| Error::msg("Vec length overflow"))?, )?; - self.primitive_vec_fast_path = exact_primitive; + + #[cfg(target_endian = "little")] + { + let byte_size = primitive_byte_cost(prim); + let total_bytes = len + .checked_mul(byte_size) + .ok_or_else(|| Error::msg("Vec byte length overflow"))?; + let pos = self.input.position() as usize; + let slice = self.input.get_ref(); + if pos + total_bytes > slice.len() { + return Err(Error::msg(format!( + "Not enough bytes for primitive vec: need {total_bytes}, have {}", + slice.len() - pos + ))); + } + let data = &slice[pos..pos + total_bytes]; + let mut access = PrimitiveVecAccess { + data, + offset: 0, + remaining: len, + element_size: byte_size, + prim, + }; + let result = visitor.visit_seq(&mut access); + // Advance by bytes actually consumed, not total_bytes, so + // the cursor is correct if the visitor short-circuits. + self.input.set_position((pos + access.offset) as u64); + return result; + } + + #[cfg(not(target_endian = "little"))] + { + self.primitive_vec_fast_path = exact_primitive; + } + } + #[cfg(feature = "bignum")] + let bignum_fast = if exact_primitive.is_none() { + match (expect.as_ref(), wire.as_ref()) { + (TypeInner::Nat, TypeInner::Nat) => Some(BigNumFastPath::Nat), + (TypeInner::Int, TypeInner::Int) => Some(BigNumFastPath::Int), + (TypeInner::Int, TypeInner::Nat) => Some(BigNumFastPath::NatAsInt), + _ => None, + } + } else { + None + }; + #[cfg(feature = "bignum")] + if let Some(fast) = bignum_fast { + self.add_cost( + len.checked_mul(3) + .ok_or_else(|| Error::msg("Vec length overflow"))?, + )?; + self.bignum_vec_fast_path = Some(fast); + self.expect_type = expect.clone(); + self.wire_type = wire.clone(); } let result = visitor.visit_seq(Compound::new( self, @@ -968,9 +1073,6 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { exact_primitive, }, )); - if exact_primitive.is_some() { - self.primitive_vec_fast_path = None; - } result } (TypeInner::Record(_), TypeInner::Record(_)) => { @@ -1192,6 +1294,81 @@ enum Style { }, } +#[cfg(target_endian = "little")] +struct PrimitiveVecAccess<'de> { + data: &'de [u8], + offset: usize, + remaining: usize, + element_size: usize, + prim: PrimitiveType, +} + +#[cfg(target_endian = "little")] +impl<'de> de::SeqAccess<'de> for PrimitiveVecAccess<'de> { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: de::DeserializeSeed<'de>, + { + use serde::de::IntoDeserializer; + if self.remaining == 0 { + return Ok(None); + } + self.remaining -= 1; + let bytes = &self.data[self.offset..self.offset + self.element_size]; + self.offset += self.element_size; + + match self.prim { + PrimitiveType::Bool => match bytes[0] { + 0 => seed.deserialize(false.into_deserializer()).map(Some), + 1 => seed.deserialize(true.into_deserializer()).map(Some), + _ => Err(Error::msg("Expect 00 or 01")), + }, + PrimitiveType::Nat8 => seed.deserialize(bytes[0].into_deserializer()).map(Some), + PrimitiveType::Int8 => seed + .deserialize((bytes[0] as i8).into_deserializer()) + .map(Some), + PrimitiveType::Nat16 => { + let v = u16::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Int16 => { + let v = i16::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Nat32 => { + let v = u32::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Int32 => { + let v = i32::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Float32 => { + let v = f32::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Nat64 => { + let v = u64::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Int64 => { + let v = i64::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + PrimitiveType::Float64 => { + let v = f64::from_le_bytes(bytes.try_into().unwrap()); + seed.deserialize(v.into_deserializer()).map(Some) + } + } + } + + fn size_hint(&self) -> Option { + Some(self.remaining) + } +} + struct Compound<'a, 'de> { de: &'a mut Deserializer<'de>, style: Style, @@ -1243,7 +1420,11 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { *len -= 1; self.de.expect_type = expect.clone(); self.de.wire_type = wire.clone(); - if exact_primitive.is_none() { + #[cfg(feature = "bignum")] + let is_fast = exact_primitive.is_some() || self.de.bignum_vec_fast_path.is_some(); + #[cfg(not(feature = "bignum"))] + let is_fast = exact_primitive.is_some(); + if !is_fast { self.de.add_cost(3)?; } seed.deserialize(&mut *self.de).map(Some) @@ -1299,6 +1480,10 @@ impl Drop for Compound<'_, '_> { // Reset fast-path state so it cannot leak if this Compound is dropped // before all elements are consumed (e.g., on an error path). self.de.primitive_vec_fast_path = None; + #[cfg(feature = "bignum")] + { + self.de.bignum_vec_fast_path = None; + } } } From 917abf8aba83e4b1964a6701ceb01d91a01e0cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Wed, 18 Mar 2026 16:51:05 +0100 Subject: [PATCH 28/43] perf(de): inline LEB128 and add map fast paths (#722) ### Motivation - Remove external `binary_parser` dependency for hot deserialization paths. ### Solution - Inline LEB128 u64/i64, length, and bool reading directly in `Deserializer`. - Add `text_fast_path` flag to skip type checks when key types are known during map deserialization. - Apply fast path to map keys/values and bignum handling. ### Meta - Removed unused `BoolValue` import. --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- rust/candid/src/de.rs | 253 +++++++++++++++++++++++++++++++++--------- 1 file changed, 203 insertions(+), 50 deletions(-) diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index d84491f92..3471b323d 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -9,7 +9,7 @@ use super::{ #[cfg(feature = "bignum")] use super::{Int, Nat}; use crate::{ - binary_parser::{BoolValue, Header, Len, PrincipalBytes}, + binary_parser::{Header, Len, PrincipalBytes}, types::subtype::{subtype_with_config, Gamma, OptReport}, }; use anyhow::{anyhow, Context}; @@ -305,6 +305,7 @@ struct Deserializer<'de> { primitive_vec_fast_path: Option, #[cfg(feature = "bignum")] bignum_vec_fast_path: Option, + text_fast_path: bool, } impl<'de> Deserializer<'de> { @@ -326,6 +327,7 @@ impl<'de> Deserializer<'de> { primitive_vec_fast_path: None, #[cfg(feature = "bignum")] bignum_vec_fast_path: None, + text_fast_path: false, }) } fn dump_state(&self) -> String { @@ -347,6 +349,89 @@ impl<'de> Deserializer<'de> { } res } + #[inline] + fn read_leb_u64(&mut self) -> Result { + self.try_read_leb_u64()? + .ok_or_else(|| Error::msg("LEB128 overflow")) + } + /// Returns `Ok(None)` on overflow (value too large for u64), `Err` on I/O error (e.g. EOF). + /// This lets callers fall through to a bignum path on overflow without swallowing real errors. + #[inline] + fn try_read_leb_u64(&mut self) -> Result> { + let slice = self.input.get_ref(); + let mut pos = self.input.position() as usize; + let end = slice.len(); + let mut result: u64 = 0; + let mut shift: u32 = 0; + loop { + if pos >= end { + return Err(Error::msg("unexpected end of LEB128")); + } + let byte = slice[pos]; + pos += 1; + let low = (byte & 0x7f) as u64; + if shift < 64 { + result |= low << shift; + } + if byte & 0x80 == 0 { + self.input.set_position(pos as u64); + return Ok(Some(result)); + } + shift += 7; + if shift >= 70 { + return Ok(None); + } + } + } + /// Returns `Ok(None)` on overflow (value too large for i64), `Err` on I/O error (e.g. EOF). + /// This lets callers fall through to a bignum path on overflow without swallowing real errors. + #[inline] + fn try_read_leb_i64(&mut self) -> Result> { + let slice = self.input.get_ref(); + let mut pos = self.input.position() as usize; + let end = slice.len(); + let mut result: i64 = 0; + let mut shift: u32 = 0; + let mut byte; + loop { + if pos >= end { + return Err(Error::msg("unexpected end of LEB128")); + } + byte = slice[pos]; + pos += 1; + let low = (byte & 0x7f) as i64; + if shift < 64 { + result |= low << shift; + } + shift += 7; + if byte & 0x80 == 0 { + break; + } + if shift >= 70 { + return Ok(None); + } + } + if shift < 64 && byte & 0x40 != 0 { + result |= !0i64 << shift; + } + self.input.set_position(pos as u64); + Ok(Some(result)) + } + #[inline] + fn read_len(&mut self) -> Result { + let val = self.read_leb_u64()?; + usize::try_from(val).map_err(|_| Error::msg("length out of usize range")) + } + #[inline] + fn read_bool_val(&mut self) -> Result { + let byte = self.input.read_u8()?; + match byte { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(Error::msg("Expect 00 or 01")), + } + } + #[inline] fn borrow_bytes(&mut self, len: usize) -> Result<&'de [u8]> { let pos = self.input.position() as usize; let slice = self.input.get_ref(); @@ -383,6 +468,7 @@ impl<'de> Deserializer<'de> { .map_err(Error::subtype)?; Ok(()) } + #[inline] fn unroll_type(&mut self) -> Result<()> { if matches!( self.expect_type.as_ref(), @@ -404,6 +490,7 @@ impl<'de> Deserializer<'de> { } Ok(()) } + #[inline] fn add_cost(&mut self, cost: usize) -> Result<()> { if let Some(n) = self.config.decoding_quota { let cost = if self.is_untyped { @@ -450,39 +537,46 @@ impl<'de> Deserializer<'de> { self.unroll_type()?; assert!(*self.expect_type == TypeInner::Int); } - let pos = self.input.position(); if !self.is_untyped { - match self.wire_type.as_ref() { - TypeInner::Int => match leb128::read::signed(&mut self.input) { - Ok(value) => { + let is_nat = matches!(self.wire_type.as_ref(), TypeInner::Nat); + let is_int = matches!(self.wire_type.as_ref(), TypeInner::Int); + if is_int { + let pos = self.input.position(); + match self.try_read_leb_i64()? { + Some(value) => { self.add_cost((self.input.position() - pos) as usize)?; return visitor.visit_i64(value); } - Err(leb128::read::Error::Overflow) => { + None => { self.input.set_position(pos); } - Err(e) => return Err(Error::msg(e)), - }, - TypeInner::Nat => match leb128::read::unsigned(&mut self.input) { - Ok(value) => { + } + } else if is_nat { + let pos = self.input.position(); + match self.try_read_leb_u64()? { + Some(value) => { self.add_cost((self.input.position() - pos) as usize)?; return visitor.visit_u64(value); } - Err(leb128::read::Error::Overflow) => { + None => { self.input.set_position(pos); } - Err(e) => return Err(Error::msg(e)), - }, - t => return Err(Error::subtype(format!("{t} cannot be deserialized to int"))), + } + } else { + return Err(Error::subtype(format!( + "{} cannot be deserialized to int", + self.wire_type + ))); } } + let bignum_pos = self.input.position(); let mut bytes = vec![0u8]; let int = match self.wire_type.as_ref() { TypeInner::Int => Int::decode(&mut self.input).map_err(Error::msg)?, TypeInner::Nat => Int(Nat::decode(&mut self.input).map_err(Error::msg)?.0.into()), t => return Err(Error::subtype(format!("{t} cannot be deserialized to int"))), }; - self.add_cost((self.input.position() - pos) as usize)?; + self.add_cost((self.input.position() - bignum_pos) as usize)?; bytes.extend_from_slice(&int.0.to_signed_bytes_le()); visitor.visit_byte_buf(bytes) } @@ -499,19 +593,19 @@ impl<'de> Deserializer<'de> { "nat" ); } - let pos = self.input.position(); if !self.is_untyped { - match leb128::read::unsigned(&mut self.input) { - Ok(value) => { + let pos = self.input.position(); + match self.try_read_leb_u64()? { + Some(value) => { self.add_cost((self.input.position() - pos) as usize)?; return visitor.visit_u64(value); } - Err(leb128::read::Error::Overflow) => { + None => { self.input.set_position(pos); } - Err(e) => return Err(Error::msg(e)), } } + let pos = self.input.position(); let mut bytes = vec![1u8]; let nat = Nat::decode(&mut self.input).map_err(Error::msg)?; self.add_cost((self.input.position() - pos) as usize)?; @@ -559,12 +653,12 @@ impl<'de> Deserializer<'de> { { self.unroll_type()?; self.check_subtype()?; - if !BoolValue::read(&mut self.input)?.0 { + if !self.read_bool_val()? { return Err(Error::msg("Opaque reference not supported")); } let mut bytes = vec![5u8]; let id = PrincipalBytes::read(&mut self.input)?; - let len = Len::read(&mut self.input)?.0; + let len = self.read_len()?; let meth = self.borrow_bytes(len)?; self.add_cost( std::cmp::max(30, id.len as usize) @@ -586,7 +680,7 @@ impl<'de> Deserializer<'de> { self.expect_type.is_blob(&self.table) && self.wire_type.is_blob(&self.table), "blob" ); - let len = Len::read(&mut self.input)?.0; + let len = self.read_len()?; self.add_cost(len.saturating_add(1))?; let blob = self.borrow_bytes(len)?; let mut bytes = Vec::with_capacity(len + 1); @@ -608,9 +702,9 @@ impl<'de> Deserializer<'de> { where V: Visitor<'de>, { - let len = Len::read(&mut self.input)?.0 as u64; + let len = self.read_len()? as u64; self.add_cost((len as usize).saturating_add(1))?; - Len::read(&mut self.input)?; + self.read_len()?; let slice_len = self.input.get_ref().len() as u64; let pos = self.input.position(); if len > slice_len || pos + len > slice_len { @@ -914,8 +1008,8 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { V: Visitor<'de>, { if self.primitive_vec_fast_path == Some(PrimitiveType::Bool) { - let res = BoolValue::read(&mut self.input)?; - return visitor.visit_bool(res.0); + let val = self.read_bool_val()?; + return visitor.visit_bool(val); } self.unroll_type()?; check!( @@ -923,8 +1017,8 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { "bool" ); self.add_cost(1)?; - let res = BoolValue::read(&mut self.input)?; - visitor.visit_bool(res.0) + let val = self.read_bool_val()?; + visitor.visit_bool(val) } fn deserialize_string(self, visitor: V) -> Result where @@ -936,12 +1030,14 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { where V: Visitor<'de>, { - self.unroll_type()?; - check!( - *self.expect_type == TypeInner::Text && *self.wire_type == TypeInner::Text, - "text" - ); - let len = Len::read(&mut self.input)?.0; + if !self.text_fast_path { + self.unroll_type()?; + check!( + *self.expect_type == TypeInner::Text && *self.wire_type == TypeInner::Text, + "text" + ); + } + let len = self.read_len()?; self.add_cost(len.saturating_add(1))?; let slice = self.borrow_bytes(len)?; let value: &str = std::str::from_utf8(slice).map_err(Error::msg)?; @@ -972,7 +1068,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { (TypeInner::Opt(t1), TypeInner::Opt(t2)) => { self.wire_type = t1.clone(); self.expect_type = t2.clone(); - if BoolValue::read(&mut self.input)?.0 { + if self.read_bool_val()? { let _guard = self.recursion_depth.guard()?; self.recoverable_visit_some(visitor) } else { @@ -1000,7 +1096,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { (TypeInner::Vec(e), TypeInner::Vec(w)) => { let expect = e.clone(); let wire = self.table.trace_type_with_depth(w, &self.recursion_depth)?; - let len = Len::read(&mut self.input)?.0; + let len = self.read_len()?; let exact_primitive = exact_primitive_type(&expect, &wire); if let Some(prim) = exact_primitive { let per_element_cost = 3 + primitive_byte_cost(prim); @@ -1106,7 +1202,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { && *self.wire_type == TypeInner::Vec(TypeInner::Nat8.into()), "vec nat8" ); - let len = Len::read(&mut self.input)?.0; + let len = self.read_len()?; self.add_cost(len.saturating_add(1))?; let bytes = self.borrow_bytes(len)?.to_owned(); visitor.visit_byte_buf(bytes) @@ -1116,7 +1212,7 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { match self.expect_type.as_ref() { TypeInner::Principal => self.deserialize_principal(visitor), TypeInner::Vec(t) if **t == TypeInner::Nat8 => { - let len = Len::read(&mut self.input)?.0; + let len = self.read_len()?; self.add_cost(len.saturating_add(1))?; let slice = self.borrow_bytes(len)?; visitor.visit_borrowed_bytes(slice) @@ -1148,11 +1244,49 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { { let expect = (ek.clone(), ev.clone()); let wire = (wk.clone(), wv.clone()); - let len = Len::read(&mut self.input)?.0; - visitor.visit_map(Compound::new( + let len = self.read_len()?; + + let key_text_fast = matches!(ek.as_ref(), TypeInner::Text) + && matches!(wk.as_ref(), TypeInner::Text); + #[cfg(feature = "bignum")] + let value_bignum_fast = match (ev.as_ref(), wv.as_ref()) { + (TypeInner::Nat, TypeInner::Nat) => Some(BigNumFastPath::Nat), + (TypeInner::Int, TypeInner::Int) => Some(BigNumFastPath::Int), + (TypeInner::Int, TypeInner::Nat) => { + Some(BigNumFastPath::NatAsInt) + } + _ => None, + }; + #[cfg(feature = "bignum")] + let any_fast = key_text_fast || value_bignum_fast.is_some(); + #[cfg(not(feature = "bignum"))] + let any_fast = key_text_fast; + + if any_fast { + self.add_cost( + len.checked_mul(7) + .ok_or_else(|| Error::msg("Map length overflow"))?, + )?; + if key_text_fast { + self.text_fast_path = true; + } + #[cfg(feature = "bignum")] + if let Some(fast) = value_bignum_fast { + self.bignum_vec_fast_path = Some(fast); + self.wire_type = wv.clone(); + } + } + + let result = visitor.visit_map(Compound::new( self, Style::Map { len, expect, wire }, - )) + )); + self.text_fast_path = false; + #[cfg(feature = "bignum")] + { + self.bignum_vec_fast_path = None; + } + result } _ => Err(Error::subtype("expect a key-value pair")), } @@ -1477,9 +1611,8 @@ impl<'de> de::SeqAccess<'de> for Compound<'_, 'de> { impl Drop for Compound<'_, '_> { fn drop(&mut self) { - // Reset fast-path state so it cannot leak if this Compound is dropped - // before all elements are consumed (e.g., on an error path). self.de.primitive_vec_fast_path = None; + self.de.text_fast_path = false; #[cfg(feature = "bignum")] { self.de.bignum_vec_fast_path = None; @@ -1568,13 +1701,21 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { ref expect, ref wire, } => { - // This only comes from deserialize_map if *len == 0 { return Ok(None); } - self.de.expect_type = expect.0.clone(); - self.de.wire_type = wire.0.clone(); *len -= 1; + #[cfg(feature = "bignum")] + let any_fast = self.de.text_fast_path || self.de.bignum_vec_fast_path.is_some(); + #[cfg(not(feature = "bignum"))] + let any_fast = self.de.text_fast_path; + if !any_fast { + self.de.add_cost(4)?; + } + if !self.de.text_fast_path { + self.de.expect_type = expect.0.clone(); + self.de.wire_type = wire.0.clone(); + } seed.deserialize(&mut *self.de).map(Some) } _ => Err(Error::msg("expect struct or map")), @@ -1586,9 +1727,21 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { { match &self.style { Style::Map { expect, wire, .. } => { - self.de.add_cost(3)?; - self.de.expect_type = expect.1.clone(); - self.de.wire_type = wire.1.clone(); + #[cfg(feature = "bignum")] + let any_fast = self.de.text_fast_path || self.de.bignum_vec_fast_path.is_some(); + #[cfg(not(feature = "bignum"))] + let any_fast = self.de.text_fast_path; + if !any_fast { + self.de.add_cost(3)?; + } + #[cfg(feature = "bignum")] + let value_fast = self.de.bignum_vec_fast_path.is_some(); + #[cfg(not(feature = "bignum"))] + let value_fast = false; + if !value_fast { + self.de.expect_type = expect.1.clone(); + self.de.wire_type = wire.1.clone(); + } seed.deserialize(&mut *self.de) } _ => { From 6b0576e5461e36ffd0a82675049ce1087b23d679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Wed, 18 Mar 2026 17:47:33 +0100 Subject: [PATCH 29/43] bench(bench): add real-world encoding/decoding benchmarks (#723) ### Motivation - Existing benchmarks miss critical decode paths; issue #603 notes zero coverage of service subtype checking. ### Solution - Add 8 benchmarks covering production patterns: fully-populated NNS neurons, schema evolution (field skipping), vec of services (exercises check_subtype), Ok/Err result variants, wide records (34+ fields), large enums (21 arms), double optionals, and multi-arg encoding/decoding. ### Details - Enable `value` feature on candid dependency for IDLValue/IDLArgs APIs. - Each benchmark reports separate encode/decode timings via bench_scope. Relates to #603 --------- Co-authored-by: Linwei Shang --- rust/bench/Cargo.toml | 2 +- rust/bench/bench.rs | 582 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 582 insertions(+), 2 deletions(-) diff --git a/rust/bench/Cargo.toml b/rust/bench/Cargo.toml index fbe32c4cc..3ef135b3e 100644 --- a/rust/bench/Cargo.toml +++ b/rust/bench/Cargo.toml @@ -9,7 +9,7 @@ name = "bench" path = "bench.rs" [dependencies] -candid = { path = "../candid" } +candid = { path = "../candid", features = ["value"] } candid_parser = { path = "../candid_parser" } canbench-rs = "0.2.0" ic-cdk = "0.17" diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 770d8aac2..7dd94f413 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -1,5 +1,7 @@ use canbench_rs::{bench, bench_fn, bench_scope, BenchResult}; -use candid::{CandidType, Decode, DecoderConfig, Deserialize, Encode, Int, Nat}; +use candid::{ + CandidType, Decode, DecoderConfig, Deserialize, Encode, IDLArgs, IDLValue, Int, Nat, Principal, +}; use std::collections::BTreeMap; #[allow(clippy::all)] @@ -330,4 +332,582 @@ fn extra_args() -> BenchResult { }) } +// Vec of fully populated complex structs (21 fields, nested vecs/maps/opts) +// Exercises the dominant real-world payload shape: list endpoints returning large records. +// The existing nns_list_proposal benchmark uses mostly-empty ProposalInfo; this uses +// fully-populated Neurons which are ~5-10x heavier per element. +#[bench(raw)] +fn nns_list_neurons() -> BenchResult { + use crate::nns::*; + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let make_neuron = |i: u64| Neuron { + id: Some(NeuronId { id: i }), + staked_maturity_e8s_equivalent: Some(1_000_000), + controller: Some(Principal::from_slice(&i.to_be_bytes())), + recent_ballots: (0..100) + .map(|j| BallotInfo { + vote: if j % 2 == 0 { 1 } else { 2 }, + proposal_id: Some(NeuronId { id: j }), + }) + .collect(), + kyc_verified: true, + neuron_type: Some(1), + not_for_profit: false, + maturity_e8s_equivalent: 500_000, + cached_neuron_stake_e8s: 10_000_000_000, + created_timestamp_seconds: 1_700_000_000 + i, + auto_stake_maturity: Some(true), + aging_since_timestamp_seconds: 1_700_000_000, + hot_keys: (0..5) + .map(|j| Principal::from_slice(&[j as u8; 10])) + .collect(), + account: serde_bytes::ByteBuf::from(vec![i as u8; 32]), + joined_community_fund_timestamp_seconds: Some(1_700_000_000), + dissolve_state: Some(DissolveState::DissolveDelaySeconds(15_778_800)), + followees: (0..10) + .map(|topic| { + ( + topic, + Followees { + followees: (0..5).map(|f| NeuronId { id: f as u64 }).collect(), + }, + ) + }) + .collect(), + neuron_fees_e8s: 10_000, + transfer: Some(NeuronStakeTransfer { + to_subaccount: serde_bytes::ByteBuf::from(vec![0u8; 32]), + neuron_stake_e8s: 10_000_000_000, + from: Some(Principal::from_slice(&[1u8; 10])), + memo: 42, + from_subaccount: serde_bytes::ByteBuf::from(vec![0u8; 32]), + transfer_timestamp: 1_700_000_000, + block_height: 1_000_000, + }), + known_neuron_data: Some(KnownNeuronData { + name: format!("neuron-{}", i), + description: Some(format!("A known neuron #{}", i)), + }), + spawn_at_timestamp_seconds: None, + }; + + let neuron_infos: Vec<(u64, nns::NeuronInfo)> = (0..100) + .map(|i| { + ( + i, + nns::NeuronInfo { + dissolve_delay_seconds: 15_778_800, + recent_ballots: (0..100) + .map(|j| BallotInfo { + vote: 1, + proposal_id: Some(NeuronId { id: j }), + }) + .collect(), + neuron_type: Some(1), + created_timestamp_seconds: 1_700_000_000, + state: 1, + stake_e8s: 10_000_000_000, + joined_community_fund_timestamp_seconds: Some(1_700_000_000), + retrieved_at_timestamp_seconds: 1_700_000_000, + known_neuron_data: Some(KnownNeuronData { + name: format!("neuron-{}", i), + description: Some(format!("Known neuron #{}", i)), + }), + voting_power: 20_000_000_000, + age_seconds: 31_557_600, + }, + ) + }) + .collect(); + + let response = nns::ListNeuronsResponse { + neuron_infos, + full_neurons: (0..100).map(make_neuron).collect(), + }; + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&response).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, nns::ListNeuronsResponse).unwrap(); + } + }) +} + +// Schema evolution — encode with a newer type (16 fields), decode with an older +// type (4 fields). Forces the decoder to skip 12 unknown fields per record, exercising +// the field-skipping mechanism that has zero coverage in the existing benchmark. +#[bench(raw)] +fn subtype_decode() -> BenchResult { + #[derive(CandidType)] + struct RecordV2 { + id: u64, + name: String, + balance: u64, + active: bool, + score: f64, + owner: Principal, + data: serde_bytes::ByteBuf, + count: u32, + extra_field_1: String, + extra_field_2: u64, + extra_field_3: Option, + extra_field_4: Vec, + extra_field_5: bool, + extra_field_6: f64, + extra_field_7: Principal, + extra_field_8: u32, + } + + #[derive(CandidType, Deserialize)] + struct RecordV1 { + id: u64, + name: String, + balance: u64, + active: bool, + } + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST); + + let records: Vec = (0..1000) + .map(|i| RecordV2 { + id: i as u64, + name: format!("record-{}", i), + balance: i as u64 * 1000, + active: i % 2 == 0, + score: i as f64 * 1.5, + owner: Principal::from_slice(&(i as u32).to_be_bytes()), + data: serde_bytes::ByteBuf::from(vec![i as u8; 64]), + count: i as u32, + extra_field_1: format!("extra-{}", i), + extra_field_2: i as u64 * 42, + extra_field_3: Some(format!("optional-{}", i)), + extra_field_4: vec![i as u8; 16], + extra_field_5: i % 3 == 0, + extra_field_6: i as f64 * 2.7, + extra_field_7: Principal::from_slice(&(i as u32 + 1000).to_be_bytes()), + extra_field_8: i as u32 * 7, + }) + .collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&records).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// Vec of service references (subscriber/registry collection pattern). +// This is the ONLY decode path where check_subtype() fires (deserialize_service +// and deserialize_function). The existing benchmark has zero coverage of this +// code path. See https://github.com/dfinity/candid/issues/603 +#[bench(raw)] +fn vec_service() -> BenchResult { + use candid::types::{Function, TypeEnv, TypeInner}; + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST); + + let method_type: candid::types::Type = TypeInner::Func(Function { + modes: vec![], + args: vec![TypeInner::Text.into()], + rets: vec![TypeInner::Nat64.into()], + }) + .into(); + + let methods: Vec<(String, candid::types::Type)> = (0..20) + .map(|i| (format!("method_{:02}", i), method_type.clone())) + .collect(); + + let service_type: candid::types::Type = TypeInner::Service(methods).into(); + let vec_service_type: candid::types::Type = TypeInner::Vec(service_type).into(); + + let services: Vec = (0..1000) + .map(|i| { + let bytes = (i as u64).to_be_bytes(); + IDLValue::Service(Principal::from_slice(&bytes)) + }) + .collect(); + + let args = IDLArgs::new(&[IDLValue::Vec(services)]); + let env = TypeEnv::new(); + let types = vec![vec_service_type.clone()]; + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + args.to_bytes_with_types(&env, &types).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + IDLArgs::from_bytes_with_types_with_config(&bytes, &env, &types, &config).unwrap(); + } + }) +} + +// Ok/Err discriminated union — the canonical ICP canister return type. +// Every canister method returns variant { Ok: Record; Err: ErrorEnum }. +// Tests heterogeneous variant payloads (struct vs enum) which the existing +// variant_list benchmark (homogeneous recursive) does not cover. +#[bench(raw)] +fn result_variant() -> BenchResult { + #[derive(CandidType, Deserialize)] + struct AccountResponse { + account: Principal, + balance_real: i128, + balance_fake: i128, + balance_ledger: i128, + last_update: u64, + overdraft_limit: u128, + name: String, + } + + #[derive(CandidType, Deserialize)] + enum AccountError { + NotAuthorized(Principal), + AccountNotFound, + InsufficientBalance { needed: u128, available: i128 }, + InternalError(String), + } + + #[derive(CandidType, Deserialize)] + enum AccountResult { + Ok(AccountResponse), + Err(AccountError), + } + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let results: Vec = (0..1000) + .map(|i| { + if i % 4 == 0 { + AccountResult::Err(AccountError::InsufficientBalance { + needed: i as u128 * 1000, + available: i as i128 * 100, + }) + } else if i % 4 == 1 { + AccountResult::Err(AccountError::NotAuthorized(Principal::from_slice( + &(i as u32).to_be_bytes(), + ))) + } else { + AccountResult::Ok(AccountResponse { + account: Principal::from_slice(&(i as u32).to_be_bytes()), + balance_real: i as i128 * 1_000_000, + balance_fake: 0, + balance_ledger: i as i128 * 1_000_000, + last_update: 1_700_000_000 + i as u64, + overdraft_limit: 10_000_000_000, + name: format!("account-{}", i), + }) + } + }) + .collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&results).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// GovernanceCachedMetrics — 34+ fields including ~10 bucket maps of +// vec record { nat64; float64 }. Tests field hash matching overhead at scale +// and exercises float64 encoding (not covered elsewhere). +#[bench(raw)] +fn wide_record() -> BenchResult { + use crate::nns::GovernanceCachedMetrics; + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let buckets: Vec<(u64, f64)> = (0..20) + .map(|i| (i * 15_778_800, i as f64 * 1_000_000.0)) + .collect(); + let count_buckets: Vec<(u64, u64)> = (0..20).map(|i| (i * 15_778_800, i * 100)).collect(); + + let metrics = GovernanceCachedMetrics { + total_maturity_e8s_equivalent: 50_000_000_000_000, + not_dissolving_neurons_e8s_buckets: buckets.clone(), + dissolving_neurons_staked_maturity_e8s_equivalent_sum: 1_000_000_000, + garbage_collectable_neurons_count: 42, + dissolving_neurons_staked_maturity_e8s_equivalent_buckets: buckets.clone(), + neurons_with_invalid_stake_count: 3, + not_dissolving_neurons_count_buckets: count_buckets.clone(), + ect_neuron_count: 100, + total_supply_icp: 500_000_000_000_000_000, + neurons_with_less_than_6_months_dissolve_delay_count: 5_000, + dissolved_neurons_count: 10_000, + community_fund_total_maturity_e8s_equivalent: 1_000_000_000_000, + total_staked_e8s_seed: 2_000_000_000_000, + total_staked_maturity_e8s_equivalent_ect: 500_000_000, + total_staked_e8s: 100_000_000_000_000_000, + not_dissolving_neurons_count: 30_000, + total_locked_e8s: 80_000_000_000_000_000, + neurons_fund_total_active_neurons: 200, + total_staked_maturity_e8s_equivalent: 5_000_000_000_000, + not_dissolving_neurons_e8s_buckets_ect: buckets.clone(), + total_staked_e8s_ect: 3_000_000_000_000, + not_dissolving_neurons_staked_maturity_e8s_equivalent_sum: 2_000_000_000, + dissolved_neurons_e8s: 500_000_000_000, + dissolving_neurons_e8s_buckets_seed: buckets.clone(), + neurons_with_less_than_6_months_dissolve_delay_e8s: 1_000_000_000_000, + not_dissolving_neurons_staked_maturity_e8s_equivalent_buckets: buckets.clone(), + dissolving_neurons_count_buckets: count_buckets.clone(), + dissolving_neurons_e8s_buckets_ect: buckets.clone(), + dissolving_neurons_count: 15_000, + dissolving_neurons_e8s_buckets: buckets.clone(), + total_staked_maturity_e8s_equivalent_seed: 700_000_000, + community_fund_total_staked_e8s: 5_000_000_000_000, + not_dissolving_neurons_e8s_buckets_seed: buckets, + timestamp_seconds: 1_700_000_000, + seed_neuron_count: 500, + }; + + let metrics_vec: Vec = std::iter::repeat(metrics).take(100).collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&metrics_vec).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// 21-arm enum with mixed payload shapes (unit, tuple, struct variants). +// The existing variant_list only tests a 2-arm recursive variant. This tests +// variant tag matching overhead at scale with heterogeneous payloads. +#[bench(raw)] +fn large_variant() -> BenchResult { + #[derive(CandidType, Deserialize, Clone)] + enum LargeAction { + Transfer { + from: Principal, + to: Principal, + amount: u64, + memo: Option, + }, + Approve { + spender: Principal, + amount: u64, + expires_at: Option, + }, + Burn { + amount: u64, + }, + Mint { + to: Principal, + amount: u64, + }, + SetFee(u64), + SetAdmin(Principal), + Pause, + Unpause, + Upgrade(serde_bytes::ByteBuf), + AddMinter(Principal), + RemoveMinter(Principal), + SetName(String), + SetSymbol(String), + SetLogo(String), + SetMetadata { + key: String, + value: String, + }, + CreateProposal { + title: String, + summary: String, + url: String, + }, + Vote { + proposal_id: u64, + vote: bool, + }, + Execute(u64), + Reject(u64), + RegisterNeuron { + stake: u64, + dissolve_delay: u64, + }, + DisburseNeuron { + neuron_id: u64, + to: Principal, + amount: Option, + }, + } + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let actions: Vec = (0..2100) + .map(|i| { + let p = Principal::from_slice(&(i as u32).to_be_bytes()); + match i % 21 { + 0 => LargeAction::Transfer { + from: p, + to: p, + amount: i as u64 * 100, + memo: Some(42), + }, + 1 => LargeAction::Approve { + spender: p, + amount: i as u64, + expires_at: Some(1_700_000_000), + }, + 2 => LargeAction::Burn { + amount: i as u64 * 50, + }, + 3 => LargeAction::Mint { + to: p, + amount: i as u64 * 1000, + }, + 4 => LargeAction::SetFee(i as u64), + 5 => LargeAction::SetAdmin(p), + 6 => LargeAction::Pause, + 7 => LargeAction::Unpause, + 8 => LargeAction::Upgrade(serde_bytes::ByteBuf::from(vec![i as u8; 32])), + 9 => LargeAction::AddMinter(p), + 10 => LargeAction::RemoveMinter(p), + 11 => LargeAction::SetName(format!("name-{}", i)), + 12 => LargeAction::SetSymbol(format!("SYM{}", i)), + 13 => LargeAction::SetLogo(format!("https://example.com/logo-{}.png", i)), + 14 => LargeAction::SetMetadata { + key: format!("key-{}", i), + value: format!("val-{}", i), + }, + 15 => LargeAction::CreateProposal { + title: format!("Proposal {}", i), + summary: format!("Summary for {}", i), + url: format!("https://example.com/{}", i), + }, + 16 => LargeAction::Vote { + proposal_id: i as u64, + vote: i % 2 == 0, + }, + 17 => LargeAction::Execute(i as u64), + 18 => LargeAction::Reject(i as u64), + 19 => LargeAction::RegisterNeuron { + stake: i as u64 * 100_000_000, + dissolve_delay: 15_778_800, + }, + _ => LargeAction::DisburseNeuron { + neuron_id: i as u64, + to: p, + amount: Some(i as u64 * 100), + }, + } + }) + .collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&actions).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// Option> — "3-state update" semantics used in real ICP APIs. +// None = don't change, Some(None) = clear, Some(Some(x)) = set. +// Exercises Candid's opt subtyping rules with double-wrapped optionals. +#[bench(raw)] +fn double_option() -> BenchResult { + #[derive(CandidType, Deserialize)] + struct UpdateRequest { + target_balance: Option>, + expiration: Option>, + spending_limit: Option>, + description: Option>, + account: Principal, + } + + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let requests: Vec = (0..1000) + .map(|i| match i % 3 { + 0 => UpdateRequest { + target_balance: None, + expiration: None, + spending_limit: None, + description: None, + account: Principal::from_slice(&(i as u32).to_be_bytes()), + }, + 1 => UpdateRequest { + target_balance: Some(None), + expiration: Some(None), + spending_limit: Some(None), + description: Some(None), + account: Principal::from_slice(&(i as u32).to_be_bytes()), + }, + _ => UpdateRequest { + target_balance: Some(Some(i as i128 * 1_000_000)), + expiration: Some(Some(1_700_000_000 + i as u64)), + spending_limit: Some(Some(i as u128 * 500)), + description: Some(Some(format!("update-{}", i))), + account: Principal::from_slice(&(i as u32).to_be_bytes()), + }, + }) + .collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&requests).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec).unwrap(); + } + }) +} + +// Multi-argument Encode!/Decode! — real canister calls often pass multiple +// arguments (e.g., principal + amount + memo). This exercises a different +// internal code path than single-struct encoding. +#[bench(raw)] +fn multi_arg() -> BenchResult { + let mut config = DecoderConfig::new(); + config.set_decoding_quota(COST).set_skipping_quota(SKIP); + + let principals: Vec = (0..1000) + .map(|i| Principal::from_slice(&(i as u64).to_be_bytes())) + .collect(); + let amounts: Vec = (0..1000).map(|i| i * 1_000_000).collect(); + let memos: Vec = (0..1000).map(|i| format!("memo-{}", i)).collect(); + + bench_fn(|| { + let bytes = { + let _p = bench_scope("1. Encoding"); + Encode!(&principals, &amounts, &memos).unwrap() + }; + { + let _p = bench_scope("2. Decoding"); + Decode!([config]; &bytes, Vec, Vec, Vec).unwrap(); + } + }) +} + fn main() {} From 27bdf42e00ee46eb38eaf7a6f452f563743b3923 Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Mon, 30 Mar 2026 10:14:57 -0400 Subject: [PATCH 30/43] ci: add publish workflow for crates.io trusted publishing (#724) ## Summary - Add a `publish.yml` workflow for publishing crates to crates.io via trusted publishing (OIDC) - Supports selective publishing of `ic_principal`, `candid`/`candid_derive` (always together), and `candid_parser` - Publishes in dependency order to avoid unmet dependency errors Co-authored-by: Claude Opus 4.6 (1M context) --- .github/workflows/publish.yml | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..1622877e6 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,65 @@ +name: Publish crates to crates.io + +on: + workflow_dispatch: + inputs: + ic_principal: + description: "Publish ic_principal" + type: boolean + default: false + candid: + description: "Publish candid and candid_derive" + type: boolean + default: false + candid_parser: + description: "Publish candid_parser" + type: boolean + default: false + +jobs: + publish: + name: Publish selected crates + runs-on: ubuntu-24.04 + if: >- + inputs.ic_principal || inputs.candid || inputs.candid_parser + + permissions: + contents: read + id-token: write # Required for trusted publishing via OIDC + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + cache: false + + - name: Authenticate with crates.io + id: auth + uses: rust-lang/crates-io-auth-action@v1 + + # Crates are ordered by dependency: publish dependencies before dependents. + # ic_principal has no workspace deps. + # candid_derive has no workspace deps. + # candid depends on ic_principal, candid_derive. + # candid_parser depends on candid. + + - name: Publish ic_principal + if: inputs.ic_principal + run: cargo publish -p ic_principal + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + + - name: Publish candid and candid_derive + if: inputs.candid + run: cargo publish -p candid_derive && cargo publish -p candid + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + + - name: Publish candid_parser + if: inputs.candid_parser + run: cargo publish -p candid_parser + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} From 36a80347a4d81388b84d00d58d1ebb8c8ee0741e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Wed, 8 Apr 2026 22:17:36 +0200 Subject: [PATCH 31/43] feat: collect all subtype errors in compatibility check + release 0.10.27 (#725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Release - `candid` 0.10.27 - `candid_parser` 0.3.1 - `didc` 0.6.1 ## Summary - The compatibility check previously stopped at the **first** incompatibility, forcing users into a fix-and-retry loop. Now it collects all breaking changes and renders them as a grouped, hierarchical report. The number of reported errors is bounded only by the interface size (no artificial cap); recursion depth is bounded by the existing stack/depth guard (~512 levels). - Error messages are clearer: "missing in new interface" instead of "is only in expected type"; "function annotation changed from query to update" instead of "Function mode mismatch". - New public API: `subtype_check_all()`, `Incompatibility`, `format_report()` in `candid`; `service_compatibility_report()` in `candid_parser`. ### Before (stops at first error) ``` Method budget_check_v1: func (BudgetCheckRequest) -> (BudgetCheckResult) query is not a subtype of func (BudgetCheckRequest/1) -> (BudgetCheckResult/1) query ``` ### After (all errors, grouped by method) ``` Error: 7 incompatible changes found: - method "config" is expected by the old interface but missing in the new one method "audit": - function annotation changed from query to update return type: record field log: nat is not a subtype of text record field count: text is not a subtype of nat method "balance": return type: text is not a subtype of nat method "transfer": input type: record field amount: nat is not a subtype of text return type: record field ok: text is not a subtype of bool record field balance: text is not a subtype of nat ``` ## Changes | File | What | |------|------| | `rust/candid/src/types/subtype.rs` | `Incompatibility` struct, `subtype_check_all()`, `format_report()`, internal `subtype_collect_()` | | `rust/candid_parser/src/utils.rs` | `service_compatibility_report()` | | `tools/didc/src/main.rs` | `didc check` uses new report | | `tools/ui/src/didjs/lib.rs` | Web UI uses new report | | `rust/candid_parser/tests/compatibility.rs` | **29 new tests** | ## Test plan - [x] 8 tests: backward-compatible changes (add opt field, add method, widen nat→int in input, etc.) must NOT be flagged - [x] 6 tests: backward-incompatible changes (remove method, change type, add required field, etc.) must be caught - [x] 6 tests: multiple errors are ALL collected (multiple methods, multiple fields, both input+return) - [x] 3 tests: error message quality (mentions method names, types, clear wording) - [x] 3 tests: hierarchical report formatting (grouping, inlining, empty for compatible) - [x] 3 tests: variant/edge cases - [x] All 175 existing tests continue to pass --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Sonnet 4.6 --- CHANGELOG.md | 19 + Cargo.lock | 16 +- rust/bench/Cargo.lock | 6 +- rust/candid/Cargo.toml | 4 +- rust/candid/src/types/internal.rs | 3 +- rust/candid/src/types/subtype.rs | 472 +++++++++++++++++++ rust/candid_derive/Cargo.toml | 2 +- rust/candid_parser/Cargo.toml | 2 +- rust/candid_parser/src/utils.rs | 19 + rust/candid_parser/tests/compatibility.rs | 538 ++++++++++++++++++++++ tools/didc/Cargo.toml | 2 +- tools/didc/src/main.rs | 20 +- tools/ui/src/didjs/lib.rs | 12 +- 13 files changed, 1095 insertions(+), 20 deletions(-) create mode 100644 rust/candid_parser/tests/compatibility.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2533fe9b1..d65e14e96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 2026-04-08 + +### didc 0.6.1 + +* Non-breaking changes: + + `didc check` now reports **all** incompatible changes at once, grouped by method, instead of stopping at the first error + + Clearer error messages: e.g. "missing in new interface" and "function annotation changed from query to update" + +### candid_parser 0.3.1 + +* Non-breaking changes: + + Add `service_compatibility_report()` returning a full grouped compatibility report as a string + +### Candid 0.10.27 + +* Non-breaking changes: + + Add `subtype_check_all()` to collect all subtype errors in one pass (previously stopped at the first) + + Add `Incompatibility` type and `format_report()` for structured, hierarchical error reporting + ## 2026-03-18 ### Candid 0.10.26 diff --git a/Cargo.lock b/Cargo.lock index 8eb17f36e..e0a09b737 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,14 +209,14 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.26" +version = "0.10.27" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.26", - "candid_parser 0.3.0", + "candid_derive 0.10.27", + "candid_parser 0.3.1", "hex", "ic_principal 0.1.2", "leb128", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.26" +version = "0.10.27" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -276,11 +276,11 @@ dependencies = [ [[package]] name = "candid_parser" -version = "0.3.0" +version = "0.3.1" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.26", + "candid 0.10.27", "codespan-reporting", "console", "convert_case", @@ -479,10 +479,10 @@ dependencies = [ [[package]] name = "didc" -version = "0.6.0" +version = "0.6.1" dependencies = [ "anyhow", - "candid_parser 0.3.0", + "candid_parser 0.3.1", "clap", "console", "hex", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index ef54d1e25..9b15a6219 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.26" +version = "0.10.27" dependencies = [ "anyhow", "binread", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.26" +version = "0.10.27" dependencies = [ "lazy_static", "proc-macro2", @@ -187,7 +187,7 @@ dependencies = [ [[package]] name = "candid_parser" -version = "0.3.0" +version = "0.3.1" dependencies = [ "anyhow", "candid", diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index 5655ede03..f5b0a8b4c 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.26" +version = "0.10.27" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.26" } +candid_derive = { path = "../candid_derive", version = "=0.10.27" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index 6e82e15cf..e11364879 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -16,9 +16,8 @@ pub struct TypeId { impl TypeId { pub fn of() -> Self { let name = std::any::type_name::(); - #[allow(function_casts_as_integer)] TypeId { - id: TypeId::of:: as usize, + id: TypeId::of:: as *const () as usize, name, } } diff --git a/rust/candid/src/types/subtype.rs b/rust/candid/src/types/subtype.rs index df67df9f7..bf3027aff 100644 --- a/rust/candid/src/types/subtype.rs +++ b/rust/candid/src/types/subtype.rs @@ -4,6 +4,7 @@ use crate::utils::RecursionDepth; use crate::{Error, Result}; use anyhow::Context; use std::collections::{HashMap, HashSet}; +use std::fmt; pub type Gamma = HashSet<(Type, Type)>; @@ -36,6 +37,477 @@ pub fn subtype_with_config( subtype_(report, gamma, env, t1, t2, &RecursionDepth::new()) } +/// A single incompatibility found during subtype checking. +#[derive(Debug, Clone)] +pub struct Incompatibility { + /// Path to the incompatible element, from outermost to innermost. + /// e.g., `["method \"transfer\"", "return type", "record field \"status\""]` + pub path: Vec, + /// Description of the specific incompatibility. + pub message: String, +} + +impl fmt::Display for Incompatibility { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.path.is_empty() { + write!(f, "{}", self.message) + } else { + for (i, segment) in self.path.iter().enumerate() { + if i > 0 { + write!(f, " > ")?; + } + write!(f, "{segment}")?; + } + write!(f, ": {}", self.message) + } + } +} + +/// Format a list of incompatibilities as a grouped, hierarchical report. +/// +/// Errors are grouped by their shared path prefixes so that, for example, +/// five errors under `method "transfer"` appear together rather than as +/// five separate top-level items. +/// +/// ```text +/// method "transfer": +/// return type: +/// - record field a: text is not a subtype of nat +/// - record field b: nat is not a subtype of bool +/// input type: +/// - missing required field amount (type nat) +/// +/// method "get_user": +/// - missing in new interface +/// ``` +pub fn format_report(errors: &[Incompatibility]) -> String { + if errors.is_empty() { + return String::new(); + } + + // Build a tree: each node is either a branch (has children keyed by path segment) + // or a leaf (has a message). + struct Node { + children: Vec<(String, Node)>, + messages: Vec, + } + + impl Node { + fn new() -> Self { + Node { + children: Vec::new(), + messages: Vec::new(), + } + } + fn child(&mut self, key: &str) -> &mut Node { + if let Some(pos) = self.children.iter().position(|(k, _)| k == key) { + &mut self.children[pos].1 + } else { + self.children.push((key.to_string(), Node::new())); + let last = self.children.len() - 1; + &mut self.children[last].1 + } + } + fn insert(&mut self, path: &[String], message: &str) { + if path.is_empty() { + self.messages.push(message.to_string()); + } else { + self.child(&path[0]).insert(&path[1..], message); + } + } + fn render(&self, out: &mut String, indent: usize) { + let pad = " ".repeat(indent); + for msg in &self.messages { + out.push_str(&pad); + out.push_str("- "); + out.push_str(msg); + out.push('\n'); + } + for (key, child) in &self.children { + // If the child has exactly one message and no sub-children, inline it. + if child.children.is_empty() && child.messages.len() == 1 { + out.push_str(&pad); + out.push_str(key); + out.push_str(": "); + out.push_str(&child.messages[0]); + out.push('\n'); + } else { + out.push_str(&pad); + out.push_str(key); + out.push_str(":\n"); + child.render(out, indent + 1); + } + } + } + } + + let mut root = Node::new(); + for e in errors { + root.insert(&e.path, &e.message); + } + + let mut out = String::new(); + root.render(&mut out, 0); + // Remove trailing newline + if out.ends_with('\n') { + out.pop(); + } + out +} + +/// Check if `t1 <: t2`, collecting **all** incompatibilities instead of stopping at the first. +/// +/// Returns an empty `Vec` when `t1` is indeed a subtype of `t2`. +/// This is intended for upgrade compatibility reports where users need to see +/// every breaking change at once. +pub fn subtype_check_all( + gamma: &mut Gamma, + env: &TypeEnv, + t1: &Type, + t2: &Type, +) -> Vec { + let mut errors = Vec::new(); + subtype_collect_( + OptReport::Warning, + gamma, + env, + t1, + t2, + &RecursionDepth::new(), + &mut Vec::new(), + &mut errors, + false, + ); + errors +} + +/// Internal collecting variant of `subtype_`. Instead of short-circuiting on +/// the first error, this continues through all fields/methods/args and pushes +/// every incompatibility it finds into `errors`. +#[allow(clippy::too_many_arguments)] +fn subtype_collect_( + report: OptReport, + gamma: &mut Gamma, + env: &TypeEnv, + t1: &Type, + t2: &Type, + depth: &RecursionDepth, + path: &mut Vec, + errors: &mut Vec, + is_input: bool, +) { + let _guard = match depth.guard() { + Ok(g) => g, + Err(_) => { + errors.push(Incompatibility { + path: path.clone(), + message: "recursion limit exceeded".to_string(), + }); + return; + } + }; + use TypeInner::*; + if t1 == t2 { + return; + } + // Handle Var/Knot (type variables / recursive types) + if matches!(t1.as_ref(), Var(_) | Knot(_)) || matches!(t2.as_ref(), Var(_) | Knot(_)) { + if !gamma.insert((t1.clone(), t2.clone())) { + return; // co-inductive: assume OK + } + let before = errors.len(); + match (t1.as_ref(), t2.as_ref()) { + (Var(id), _) => subtype_collect_( + report, + gamma, + env, + env.rec_find_type_with_depth(id, depth).unwrap(), + t2, + depth, + path, + errors, + is_input, + ), + (_, Var(id)) => subtype_collect_( + report, + gamma, + env, + t1, + env.rec_find_type_with_depth(id, depth).unwrap(), + depth, + path, + errors, + is_input, + ), + (Knot(id), _) => subtype_collect_( + report, + gamma, + env, + &find_type(id).unwrap(), + t2, + depth, + path, + errors, + is_input, + ), + (_, Knot(id)) => subtype_collect_( + report, + gamma, + env, + t1, + &find_type(id).unwrap(), + depth, + path, + errors, + is_input, + ), + (_, _) => unreachable!(), + }; + if errors.len() > before { + gamma.remove(&(t1.clone(), t2.clone())); + } + return; + } + match (t1.as_ref(), t2.as_ref()) { + (_, Reserved) => (), + (Empty, _) => (), + (Nat, Int) => (), + (Vec(ty1), Vec(ty2)) => { + subtype_collect_(report, gamma, env, ty1, ty2, depth, path, errors, is_input); + } + (Null, Opt(_)) => (), + // For opt rules we delegate to the existing subtype_ to test the condition, + // since these are probes, not things that generate multiple independent errors. + (Opt(ty1), Opt(ty2)) if subtype_(report, gamma, env, ty1, ty2, depth).is_ok() => {} + (_, Opt(ty2)) + if subtype_(report, gamma, env, t1, ty2, depth).is_ok() + && !matches!( + env.trace_type_with_depth(ty2, depth) + .map(|t| t.as_ref().clone()), + Ok(Null | Reserved | Opt(_)) + ) => {} + (_, Opt(_)) => { + let msg = format!("WARNING: {t1} <: {t2} due to special subtyping rules involving optional types/fields (see https://github.com/dfinity/candid/blob/c7659ca/spec/Candid.md#upgrading-and-subtyping). This means the two interfaces have diverged, which could cause data loss."); + match report { + OptReport::Silence => (), + OptReport::Warning => eprintln!("{msg}"), + OptReport::Error => { + errors.push(Incompatibility { + path: path.clone(), + message: msg, + }); + } + }; + } + (Record(fs1), Record(fs2)) => { + let fields: HashMap<_, _> = fs1.iter().map(|Field { id, ty }| (id, ty)).collect(); + for Field { id, ty: ty2 } in fs2 { + match fields.get(id) { + Some(ty1) => { + path.push(format!("record field {id}")); + subtype_collect_( + report, gamma, env, ty1, ty2, depth, path, errors, is_input, + ); + path.pop(); + } + None => { + let is_optional = env + .trace_type_with_depth(ty2, depth) + .map(|t| matches!(t.as_ref(), Null | Reserved | Opt(_))) + .unwrap_or(false); + if !is_optional { + errors.push(Incompatibility { + path: path.clone(), + message: if is_input { + format!( + "new service requires field {id} (type {ty2}), \ + which old callers don't provide and is not optional" + ) + } else { + format!( + "new type is missing required field {id} (type {ty2}), \ + which is expected by the old type and is not optional" + ) + }, + }); + } + } + } + } + } + (Variant(fs1), Variant(fs2)) => { + let fields: HashMap<_, _> = fs2.iter().map(|Field { id, ty }| (id, ty)).collect(); + for Field { id, ty: ty1 } in fs1 { + match fields.get(id) { + Some(ty2) => { + path.push(format!("variant field {id}")); + subtype_collect_( + report, gamma, env, ty1, ty2, depth, path, errors, is_input, + ); + path.pop(); + } + None => { + errors.push(Incompatibility { + path: path.clone(), + message: if is_input { + format!( + "old callers may send variant case {id}, \ + which the new service no longer handles" + ) + } else { + format!( + "new variant has field {id} that does not exist in the old type" + ) + }, + }); + } + } + } + } + (Service(ms1), Service(ms2)) => { + let meths: HashMap<_, _> = ms1.iter().cloned().collect(); + for (name, ty2) in ms2 { + match meths.get(name) { + Some(ty1) => { + path.push(format!("method \"{name}\"")); + subtype_collect_(report, gamma, env, ty1, ty2, depth, path, errors, false); + path.pop(); + } + None => { + errors.push(Incompatibility { + path: path.clone(), + message: format!( + "method \"{name}\" is expected by the old interface but missing in the new one" + ), + }); + } + } + } + } + (Func(f1), Func(f2)) => { + if f1.modes != f2.modes { + errors.push(Incompatibility { + path: path.clone(), + message: format!( + "function annotation changed from {old} to {new}", + old = if f2.modes.is_empty() { + "update".to_string() + } else { + pp_modes(&f2.modes) + }, + new = if f1.modes.is_empty() { + "update".to_string() + } else { + pp_modes(&f1.modes) + }, + ), + }); + // Don't return early - also check arg/ret compatibility + } + // Check each argument directly instead of wrapping in a tuple record, + // so we get clean error paths like "input argument 1" instead of "record field 0". + check_func_params( + report, gamma, env, &f2.args, &f1.args, depth, path, errors, "input", true, + ); + check_func_params( + report, gamma, env, &f1.rets, &f2.rets, depth, path, errors, "return", false, + ); + } + (Class(_, t), _) => { + subtype_collect_(report, gamma, env, t, t2, depth, path, errors, is_input); + } + (_, Class(_, t)) => { + subtype_collect_(report, gamma, env, t1, t, depth, path, errors, is_input); + } + (Unknown, _) => unreachable!(), + (_, Unknown) => unreachable!(), + (_, _) => { + errors.push(Incompatibility { + path: path.clone(), + message: format!("{t1} is not a subtype of {t2}"), + }); + } + } +} + +/// Check function parameters (args or rets) for subtype compatibility, +/// collecting all errors. Handles the record-tuple wrapping so that single-arg +/// functions don't produce misleading "record field 0" paths. +/// +/// For inputs (contravariant): `sub_params` = old args, `sup_params` = new args. +/// For outputs (covariant): `sub_params` = new rets, `sup_params` = old rets. +#[allow(clippy::too_many_arguments)] +fn check_func_params( + report: OptReport, + gamma: &mut Gamma, + env: &TypeEnv, + sub_params: &[Type], + sup_params: &[Type], + depth: &RecursionDepth, + path: &mut Vec, + errors: &mut Vec, + label: &str, // "input" or "return" + is_input: bool, // affects wording +) { + // Use the same tuple wrapping as the original subtype_ for correctness, + // but when there's a single parameter, check it directly to avoid noise. + if sub_params.len() == 1 && sup_params.len() == 1 { + path.push(format!("{label} type")); + subtype_collect_( + report, + gamma, + env, + &sub_params[0], + &sup_params[0], + depth, + path, + errors, + is_input, + ); + path.pop(); + } else { + let sub_tuple = to_tuple(sub_params); + let sup_tuple = to_tuple(sup_params); + path.push(if sub_params.len() == sup_params.len() { + format!("{label} types") + } else if is_input { + // sub_params = old args, sup_params = new args (contravariant swap) + format!( + "{label} types (old has {} arg{}, new has {})", + sub_params.len(), + if sub_params.len() == 1 { "" } else { "s" }, + sup_params.len() + ) + } else { + format!( + "{label} types (old has {} value{}, new has {})", + sup_params.len(), + if sup_params.len() == 1 { "" } else { "s" }, + sub_params.len() + ) + }); + subtype_collect_( + report, gamma, env, &sub_tuple, &sup_tuple, depth, path, errors, is_input, + ); + path.pop(); + } +} + +fn pp_modes(modes: &[super::internal::FuncMode]) -> String { + if modes.is_empty() { + return String::new(); + } + modes + .iter() + .map(|m| match m { + super::internal::FuncMode::Oneway => "oneway", + super::internal::FuncMode::Query => "query", + super::internal::FuncMode::CompositeQuery => "composite_query", + }) + .collect::>() + .join(" ") +} + fn subtype_( report: OptReport, gamma: &mut Gamma, diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index 9aee2aa3f..efd23f980 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.26" +version = "0.10.27" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml index 3293440d1..dbb4edffe 100644 --- a/rust/candid_parser/Cargo.toml +++ b/rust/candid_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid_parser" -version = "0.3.0" +version = "0.3.1" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] diff --git a/rust/candid_parser/src/utils.rs b/rust/candid_parser/src/utils.rs index 1c7df1ca2..03b0d333e 100644 --- a/rust/candid_parser/src/utils.rs +++ b/rust/candid_parser/src/utils.rs @@ -39,6 +39,25 @@ pub fn service_compatible(new: CandidSource, old: CandidSource) -> Result<()> { Ok(()) } +/// Check compatibility of two service types, returning **all** incompatibilities +/// instead of stopping at the first one. +/// +/// Returns an empty `Vec` when the new interface is backward-compatible with the old one. +pub fn service_compatibility_report( + new: CandidSource, + old: CandidSource, +) -> Result> { + let (mut env, t1) = new.load()?; + let t1 = t1.ok_or_else(|| Error::msg("new interface has no main service type"))?; + let (env2, t2) = old.load()?; + let t2 = t2.ok_or_else(|| Error::msg("old interface has no main service type"))?; + let mut gamma = std::collections::HashSet::new(); + let t2 = env.merge_type(env2, t2); + Ok(candid::types::subtype::subtype_check_all( + &mut gamma, &env, &t1, &t2, + )) +} + /// Check structural equality of two service types pub fn service_equal(left: CandidSource, right: CandidSource) -> Result<()> { let (mut env, t1) = left.load()?; diff --git a/rust/candid_parser/tests/compatibility.rs b/rust/candid_parser/tests/compatibility.rs new file mode 100644 index 000000000..2671fe924 --- /dev/null +++ b/rust/candid_parser/tests/compatibility.rs @@ -0,0 +1,538 @@ +use candid::types::subtype::{format_report, Incompatibility}; +use candid_parser::utils::{service_compatibility_report, service_compatible, CandidSource}; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +fn check_compatible(new: &str, old: &str, desc: &str) { + service_compatible(CandidSource::Text(new), CandidSource::Text(old)) + .unwrap_or_else(|e| panic!("[{desc}] expected compatible, got error: {e}")); +} + +fn check_incompatible(new: &str, old: &str, desc: &str) { + assert!( + service_compatible(CandidSource::Text(new), CandidSource::Text(old)).is_err(), + "[{desc}] expected incompatible, but check passed" + ); +} + +fn get_errors(new: &str, old: &str) -> Vec { + service_compatibility_report(CandidSource::Text(new), CandidSource::Text(old)) + .expect("failed to load interfaces") +} + +fn error_strings(new: &str, old: &str) -> Vec { + get_errors(new, old) + .into_iter() + .map(|e| e.to_string()) + .collect() +} + +// =========================================================================== +// Backward-compatible changes must PASS (no false positives) +// =========================================================================== + +#[test] +fn compatible_service_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "identical service", + "service : { greet : (text) -> (text) }", + "service : { greet : (text) -> (text) }", + ), + ( + "add new method", + "service : { greet : (text) -> (text); hello : () -> (text) }", + "service : { greet : (text) -> (text) }", + ), + ( + "widen input nat→int (contravariant)", + "service : { pay : (int) -> () }", + "service : { pay : (nat) -> () }", + ), + ( + "narrow return int→nat (covariant, nat <: int)", + "service : { get : () -> (nat) }", + "service : { get : () -> (int) }", + ), + ]; + for &(desc, new, old) in cases { + check_compatible(new, old, desc); + } +} + +#[test] +fn compatible_record_field_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "add opt field to input record", + "type R = record { name : text; age : opt nat }; service : { f : (R) -> () }", + "type R = record { name : text }; service : { f : (R) -> () }", + ), + ( + "add opt field to return record", + "type R = record { id : nat; extra : opt text }; service : { f : () -> (R) }", + "type R = record { id : nat }; service : { f : () -> (R) }", + ), + ( + "add null field to return record", + "service : { f : () -> (record { a : nat; b : null }) }", + "service : { f : () -> (record { a : nat }) }", + ), + ( + "add reserved field to return record", + "service : { f : () -> (record { a : nat; b : reserved }) }", + "service : { f : () -> (record { a : nat }) }", + ), + ( + "remove non-opt field from input record (extra fields in subtype are fine)", + "type R = record { name : text }; service : { f : (R) -> () }", + "type R = record { name : text; age : nat }; service : { f : (R) -> () }", + ), + ]; + for &(desc, new, old) in cases { + check_compatible(new, old, desc); + } +} + +#[test] +fn compatible_variant_and_vec_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "add variant case to input (contravariant: old callers still match)", + "type V = variant { a; b; c }; service : { f : (V) -> () }", + "type V = variant { a; b }; service : { f : (V) -> () }", + ), + ( + "identical vec types", + "service : { f : () -> (vec nat) }", + "service : { f : () -> (vec nat) }", + ), + ]; + for &(desc, new, old) in cases { + check_compatible(new, old, desc); + } +} + +// =========================================================================== +// Backward-INCOMPATIBLE changes must be caught (no false negatives) +// =========================================================================== + +#[test] +fn incompatible_method_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "remove method", + "service : { greet : (text) -> (text) }", + "service : { greet : (text) -> (text); hello : () -> (text) }", + ), + ( + "change func mode query→update", + "service : { get : () -> (nat) }", + "service : { get : () -> (nat) query }", + ), + ( + "change return type nat→text", + "service : { get : () -> (text) }", + "service : { get : () -> (nat) }", + ), + ( + "change input type nat→text", + "service : { set : (text) -> () }", + "service : { set : (nat) -> () }", + ), + ( + "widen return nat→int (covariant: int (int) }", + "service : { get : () -> (nat) }", + ), + ]; + for &(desc, new, old) in cases { + check_incompatible(new, old, desc); + } +} + +#[test] +fn incompatible_record_and_variant_changes() { + let cases: &[(&str, &str, &str)] = &[ + ( + "add required field to input record", + "type R = record { name : text; age : nat }; service : { f : (R) -> () }", + "type R = record { name : text }; service : { f : (R) -> () }", + ), + ( + "remove required field from return record", + "type R = record { name : text }; service : { f : () -> (R) }", + "type R = record { name : text; age : nat }; service : { f : () -> (R) }", + ), + ( + "remove variant case from input (old callers may send it)", + "type V = variant { start; stop }; service : { f : (V) -> () }", + "type V = variant { start; stop; pause }; service : { f : (V) -> () }", + ), + ( + "change vec element type", + "service : { f : () -> (vec text) }", + "service : { f : () -> (vec nat) }", + ), + ]; + for &(desc, new, old) in cases { + check_incompatible(new, old, desc); + } +} + +// =========================================================================== +// ALL incompatibilities collected (not just the first) +// =========================================================================== + +#[test] +fn collects_all_incompatible_methods() { + let old = r#"service : { + method_a : (nat) -> (nat); + method_b : (text) -> (text); + method_c : () -> (nat); + method_d : () -> (); + }"#; + // method_a: OK, method_b: removed, method_c: return changed, method_d: removed + let new = r#"service : { + method_a : (nat) -> (nat); + method_c : () -> (text); + }"#; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 3, "got: {errors:?}"); + + let joined = errors.join("\n"); + for name in ["method_b", "method_c", "method_d"] { + assert!(joined.contains(name), "missing {name} in: {joined}"); + } + assert!( + !joined.contains("method_a"), + "method_a is compatible, should not appear: {joined}" + ); +} + +#[test] +fn collects_all_incompatible_record_fields() { + let old = "type R = record { a : nat; b : text; c : bool }; service : { f : () -> (R) }"; + let new = "type R = record { a : text; b : nat; c : bool }; service : { f : () -> (R) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 2, "got: {errors:?}"); + + let joined = errors.join("\n"); + assert!( + joined.contains("record field a"), + "missing field a: {joined}" + ); + assert!( + joined.contains("record field b"), + "missing field b: {joined}" + ); +} + +#[test] +fn collects_both_input_and_return_errors() { + let old = "service : { call : (nat) -> (nat) }"; + let new = "service : { call : (text) -> (bool) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 2, "got: {errors:?}"); + + let joined = errors.join("\n"); + assert!( + joined.contains("input type"), + "missing input error: {joined}" + ); + assert!( + joined.contains("return type"), + "missing return error: {joined}" + ); +} + +#[test] +fn collects_variant_field_errors() { + // Old callers may send variant cases b or c; new input must accept them + let old = "type V = variant { a; b; c }; service : { f : (V) -> () }"; + let new = "type V = variant { a }; service : { f : (V) -> () }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 2, "got: {errors:?}"); + + let joined = errors.join("\n"); + assert!(joined.contains("b"), "missing variant b: {joined}"); + assert!(joined.contains("c"), "missing variant c: {joined}"); +} + +// =========================================================================== +// Error message quality + path context +// =========================================================================== + +#[test] +fn missing_method_error_is_clear() { + let old = "service : { transfer : (nat) -> (); balance : () -> (nat) }"; + let new = "service : { balance : () -> (nat) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 1); + assert!(errors[0].contains("transfer"), "should name the method"); + assert!(errors[0].contains("missing"), "should say 'missing'"); +} + +#[test] +fn type_mismatch_error_names_both_types() { + let old = "service : { f : () -> (nat) }"; + let new = "service : { f : () -> (text) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 1); + assert!( + errors[0].contains("text") && errors[0].contains("nat"), + "should mention both types: {}", + errors[0] + ); +} + +#[test] +fn missing_required_field_error_is_clear() { + let old = "type R = record { name : text; age : nat }; service : { f : () -> (R) }"; + let new = "type R = record { name : text }; service : { f : () -> (R) }"; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 1); + let msg = &errors[0]; + assert!(msg.contains("age"), "should mention field name: {msg}"); + assert!( + msg.contains("missing") || msg.contains("not optional"), + "should explain the problem: {msg}" + ); +} + +#[test] +fn nested_path_shows_full_context() { + let old = r#" + type Inner = record { x : nat }; + type Outer = record { inner : Inner }; + service : { get : () -> (Outer) } + "#; + let new = r#" + type Inner = record { x : text }; + type Outer = record { inner : Inner }; + service : { get : () -> (Outer) } + "#; + let errors = error_strings(new, old); + assert_eq!(errors.len(), 1, "got: {errors:?}"); + let msg = &errors[0]; + assert!(msg.contains("method"), "path should include method: {msg}"); + assert!( + msg.contains("return type"), + "path should include return type: {msg}" + ); + assert!( + msg.contains("record field inner"), + "path should include outer field: {msg}" + ); + assert!( + msg.contains("record field x"), + "path should include inner field: {msg}" + ); +} + +// =========================================================================== +// Multi-arg functions +// =========================================================================== + +#[test] +fn multi_arg_function_incompatibilities() { + let old = "service : { f : (nat, text) -> (bool, nat) }"; + let new = "service : { f : (nat, bool) -> (bool, text) }"; + // Input: old (nat, text) must <: new (nat, bool) → field 1 text (text); g : () -> () }", + "service : { f : (text) -> (text) }", + ), + ( + "service : { f : (int) -> () }", + "service : { f : (nat) -> () }", + ), + ]; + for (new, old) in compatible { + let report = get_errors(new, old); + assert!(report.is_empty(), "report should be empty for compatible"); + assert!( + service_compatible(CandidSource::Text(new), CandidSource::Text(old)).is_ok(), + "service_compatible should pass for compatible" + ); + } + + let incompatible = [ + ( + "service : { f : (text) -> (text) }", + "service : { f : (text) -> (text); g : () -> () }", + ), + ( + "service : { f : (nat) -> () }", + "service : { f : (int) -> () }", + ), + ]; + for (new, old) in incompatible { + let report = get_errors(new, old); + assert!(!report.is_empty(), "report should have errors"); + assert!( + service_compatible(CandidSource::Text(new), CandidSource::Text(old)).is_err(), + "service_compatible should fail" + ); + } +} + +// =========================================================================== +// Hierarchical report formatting +// =========================================================================== + +#[test] +fn format_report_groups_by_method_and_nests() { + let old = r#"service : { + transfer : (record { from : text; to : text; amount : nat }) -> (record { ok : bool; balance : nat }); + balance : () -> (nat); + audit : () -> (record { count : nat; log : text }) query; + config : () -> (record { flag : bool }); + }"#; + let new = r#"service : { + transfer : (record { from : text; to : text; amount : text }) -> (record { ok : text; balance : text }); + balance : () -> (text); + audit : () -> (record { count : text; log : nat }); + }"#; + let errors = get_errors(new, old); + assert!(errors.len() >= 6, "expected many errors, got: {errors:?}"); + + let report = format_report(&errors); + + // Each method should appear exactly once as a group header + for method in ["transfer", "balance", "config"] { + let header_count = report + .lines() + .filter(|l| { + let trimmed = l.trim_start(); + trimmed.starts_with(&format!("method \"{method}\"")) + || trimmed.contains(&format!("method \"{method}\"")) + }) + .count(); + assert!( + header_count <= 1, + "{method} should appear at most once as header, got {header_count} in:\n{report}" + ); + } + + // Should have indented sub-groups + assert!( + report.contains(" return type") || report.contains(" input type"), + "should have indented sub-groups:\n{report}" + ); +} + +#[test] +fn format_report_inlines_single_leaf() { + let old = "service : { get : () -> (nat) }"; + let new = "service : { get : () -> (text) }"; + let report = format_report(&get_errors(new, old)); + // Single error under method > return type should inline compactly + assert!( + report.lines().count() <= 3, + "single error should be compact:\n{report}" + ); +} + +#[test] +fn format_report_handles_path_and_pathless_errors() { + // Pathless errors (e.g. top-level type mismatch) should render as "- message" + let errors = vec![ + Incompatibility { + path: vec![], + message: "top-level mismatch".to_string(), + }, + Incompatibility { + path: vec!["method \"foo\"".to_string()], + message: "missing in new interface".to_string(), + }, + ]; + let report = format_report(&errors); + assert!( + report.contains("- top-level mismatch"), + "pathless error should render with bullet:\n{report}" + ); + assert!( + report.contains("method \"foo\""), + "pathed error should render:\n{report}" + ); +} + +// =========================================================================== +// Input-side message wording +// =========================================================================== + +#[test] +fn input_record_required_field_added_message_names_correct_side() { + let old = "type R = record { name : text }; service : { f : (R) -> () }"; + let new = "type R = record { name : text; age : nat }; service : { f : (R) -> () }"; + let errors = error_strings(new, old); + assert_eq!( + errors.len(), + 1, + "expected exactly one error, got: {errors:?}" + ); + let msg = &errors[0]; + assert!( + !msg.contains("new type is missing"), + "message incorrectly blames the new type for the missing field: {msg}" + ); + assert!( + msg.contains("age"), + "message should mention the field name: {msg}" + ); +} + +#[test] +fn input_variant_case_removed_message_names_correct_side() { + let old = "type V = variant { a; b }; service : { f : (V) -> () }"; + let new = "type V = variant { a }; service : { f : (V) -> () }"; + let errors = error_strings(new, old); + assert_eq!( + errors.len(), + 1, + "expected exactly one error, got: {errors:?}" + ); + let msg = &errors[0]; + assert!( + !msg.contains("new variant has field"), + "message incorrectly says the new variant has the dropped case: {msg}" + ); + assert!( + msg.contains("b"), + "message should mention the variant case name: {msg}" + ); +} + +#[test] +fn input_arg_count_increase_message_has_correct_counts() { + let old = "service : { f : (nat) -> () }"; + let new = "service : { f : (nat, text) -> () }"; + let errors = error_strings(new, old); + assert!(!errors.is_empty(), "expected at least one error, got none"); + let msg = errors + .iter() + .find(|e| e.contains("arg")) + .unwrap_or_else(|| panic!("no arg-count error found in: {errors:?}")); + assert!( + msg.contains("old has 1") && msg.contains("new has 2"), + "arg-count message has wrong old/new values: {msg}" + ); +} diff --git a/tools/didc/Cargo.toml b/tools/didc/Cargo.toml index 52f4f9448..3eecbdd71 100644 --- a/tools/didc/Cargo.toml +++ b/tools/didc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "didc" -version = "0.6.0" +version = "0.6.1" authors = ["DFINITY Team"] edition = "2021" diff --git a/tools/didc/src/main.rs b/tools/didc/src/main.rs index f22ce6ca8..5495319ac 100644 --- a/tools/didc/src/main.rs +++ b/tools/didc/src/main.rs @@ -191,7 +191,25 @@ fn main() -> Result<()> { if strict { subtype::equal(&mut gamma, &env, &t1, &t2)?; } else { - subtype::subtype(&mut gamma, &env, &t1, &t2)?; + let errors = subtype::subtype_check_all(&mut gamma, &env, &t1, &t2); + if !errors.is_empty() { + let report = subtype::format_report(&errors); + eprintln!( + "{} {} incompatible change{} found:\n", + style("Error:").red().bold(), + errors.len(), + if errors.len() == 1 { "" } else { "s" } + ); + for line in report.lines() { + eprintln!(" {line}"); + } + eprintln!(); + bail!( + "new interface is not backward compatible ({} breaking change{})", + errors.len(), + if errors.len() == 1 { "" } else { "s" } + ); + } } } _ => { diff --git a/tools/ui/src/didjs/lib.rs b/tools/ui/src/didjs/lib.rs index a3a932708..afbd2800d 100644 --- a/tools/ui/src/didjs/lib.rs +++ b/tools/ui/src/didjs/lib.rs @@ -69,7 +69,17 @@ fn subtype(new: String, old: String) -> Result<(), String> { let old_actor = check_prog(&mut old_env, &old).unwrap().unwrap(); let mut gamma = std::collections::HashSet::new(); let old_actor = new_env.merge_type(old_env, old_actor); - subtype::subtype(&mut gamma, &new_env, &new_actor, &old_actor).map_err(|e| e.to_string()) + let errors = subtype::subtype_check_all(&mut gamma, &new_env, &new_actor, &old_actor); + if errors.is_empty() { + Ok(()) + } else { + let report = subtype::format_report(&errors); + Err(format!( + "{} incompatible change{} found:\n\n{report}", + errors.len(), + if errors.len() == 1 { "" } else { "s" } + )) + } } fn retrieve(path: &str) -> Option<(&str, &'static [u8])> { From 39ef0989c4f7bdd71a0a68fdaf0f1146887e692c Mon Sep 17 00:00:00 2001 From: slawomirbabicz <111378977+slawomirbabicz@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:34:18 +0200 Subject: [PATCH 32/43] chore: pin GitHub Actions to commit SHAs (#726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Pin GitHub Actions to commit SHAs GitHub Actions referenced by tag (e.g. `actions/checkout@v4`) use a mutable pointer — the tag owner can move it to a different commit at any time, including a malicious one. This is the attack vector used in the tj-actions/changed-files incident (CVE-2025-30066). Pinning to a full 40-character commit SHA makes the reference immutable. The `# tag` comment preserves human readability so reviewers can tell which version is pinned. Important: a SHA can also originate from a forked repository. A malicious actor can fork an action, push a compromised commit to the fork, and the SHA will resolve — but it won't exist in the upstream canonical repo. Each SHA in this PR was verified against the action's canonical repository (not a fork). ### Changes - `actions/checkout@v4` -> `actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1` - Version: v4.3.1 | Latest: v6.0.2 | Release age: 88d - Commit: https://github.com/actions/checkout/commit/34e114876b0b11c390a56381ad16ebd13914f8d5 - `actions/setup-python@v4` -> `actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1` - Version: v4.9.1 | Latest: v6.2.0 | Release age: 76d - Commit: https://github.com/actions/setup-python/commit/7f4fc3e22c37d6ff65e88745f38bd3157c663f7c - `actions/cache@v4` -> `actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0` - Version: v4.3.0 | Latest: v5.0.4 | Release age: 20d - Commit: https://github.com/actions/cache/commit/0057852bfaa89a56745cba8c7296529d2fc39830 - `thollander/actions-comment-pull-request@v2` -> `thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0` - Version: v2.5.0 | Latest: v3.0.1 | Release age: 522d - Commit: https://github.com/thollander/actions-comment-pull-request/commit/fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 - `cachix/install-nix-action@v12` -> `cachix/install-nix-action@07da2520eebede906fbeefa9dd0a2b635323909d # v12` - Version: v12 | Latest: v31.10.3 | Release age: 12d - Commit: https://github.com/cachix/install-nix-action/commit/07da2520eebede906fbeefa9dd0a2b635323909d - `actions/checkout@v3` -> `actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0` - Version: v3.6.0 | Latest: v6.0.2 | Release age: 88d - Commit: https://github.com/actions/checkout/commit/f43a0e5ff2bd294095638e18286ca9a3d1956744 - `EmbarkStudios/cargo-deny-action@v1` -> `EmbarkStudios/cargo-deny-action@3f4a782664881cf5725d0ffd23969fcce89fd868 # v1.6.3` - Version: v1.6.3 | Latest: v2.0.15 | Release age: 90d - Commit: https://github.com/EmbarkStudios/cargo-deny-action/commit/3f4a782664881cf5725d0ffd23969fcce89fd868 - `actions/checkout@v6` -> `actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2` - Version: v6.0.2 | Latest: v6.0.2 | Release age: 88d - Commit: https://github.com/actions/checkout/commit/de0fac2e4500dabe0009e67214ff5f5447ce83dd - `actions-rust-lang/setup-rust-toolchain@v1` -> `actions-rust-lang/setup-rust-toolchain@150fca883cd4034361b621bd4e6a9d34e5143606 # v1.15.4` - Version: v1.15.4 | Latest: v1.15.4 | Release age: 24d - Commit: https://github.com/actions-rust-lang/setup-rust-toolchain/commit/150fca883cd4034361b621bd4e6a9d34e5143606 - `rust-lang/crates-io-auth-action@v1` -> `rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec # v1` - Version: v1 | Latest: v1.0.4 | Release age: 15d - Commit: https://github.com/rust-lang/crates-io-auth-action/commit/b7e9a28eded4986ec6b1fa40eeee8f8f165559ec - `actions-rs/cargo@v1` -> `actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3` - Version: v1.0.3 | Latest: v1.0.1 | Release age: 2397d - Commit: https://github.com/actions-rs/cargo/commit/844f36862e911db73fe0815f00a4a2602c279505 - `actions/upload-artifact@v4` -> `actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2` - Version: v4.6.2 | Latest: v3.2.2 | Release age: 22d - Commit: https://github.com/actions/upload-artifact/commit/ea165f8d65b6e75b540449e92b4886f43607fa02 - `actions/download-artifact@v4` -> `actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0` - Version: v4.3.0 | Latest: v3.1.0-node20 | Release age: 22d - Commit: https://github.com/actions/download-artifact/commit/d3f86a106a0bac45b974a628896c90dbdf5c8093 - Warnings: 1 security advisory(ies) found, Action has 1 known advisory(ies) - `svenstaro/upload-release-action@v2` -> `svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # 2.11.5` - Version: 2.11.5 | Latest: 2.11.5 | Release age: 22d - Commit: https://github.com/svenstaro/upload-release-action/commit/29e53e917877a24fad85510ded594ab3c9ca12de - `actions/setup-node@v4` -> `actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0` - Version: v4.4.0 | Latest: v6.3.0 | Release age: 35d - Commit: https://github.com/actions/setup-node/commit/49933ea5288caeca8642d1e84afbd3f7d6820020 - `pnpm/action-setup@v3` -> `pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3` - Version: v3 | Latest: v5.0.0 | Release age: 22d - Commit: https://github.com/pnpm/action-setup/commit/a3252b78c470c02df07e9d59298aecedc3ccdd6d ### Files modified - `.github/workflows/bench.yml` - `.github/workflows/coq.yml` - `.github/workflows/license.yml` - `.github/workflows/publish.yml` - `.github/workflows/release.yml` - `.github/workflows/rust.yml` - `.github/workflows/tools.yml` ### Security warnings - 1 security advisory(ies) found - Action has 1 known advisory(ies) --------- Co-authored-by: Linwei Shang --- .github/workflows/bench.yml | 10 +++++----- .github/workflows/coq.yml | 4 ++-- .github/workflows/license.yml | 4 ++-- .github/workflows/publish.yml | 6 +++--- .github/workflows/release.yml | 18 +++++++++--------- .github/workflows/rust.yml | 6 +++--- .github/workflows/tools.yml | 8 ++++---- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 40d84c187..a576d5c0a 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -9,13 +9,13 @@ jobs: CARGO_TERM_COLOR: always # Force Cargo to use colors TERM: xterm-256color steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Checkout base branch - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: ref: ${{ github.base_ref }} path: main/ - - uses: actions/setup-python@v4 + - uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 with: python-version: "3.10" - name: Install Python dependencies @@ -23,7 +23,7 @@ jobs: python -m pip install --upgrade pip pip install pyyaml - name: Cache cargo build - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cargo @@ -53,7 +53,7 @@ jobs: echo "" } > /tmp/message.txt - name: Post comment - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: filePath: /tmp/message.txt comment_tag: canbench diff --git a/.github/workflows/coq.yml b/.github/workflows/coq.yml index ec56f00df..fd6961682 100644 --- a/.github/workflows/coq.yml +++ b/.github/workflows/coq.yml @@ -11,8 +11,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v12 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: cachix/install-nix-action@07da2520eebede906fbeefa9dd0a2b635323909d # v12 #- run: nix-build s coq # The above would also build the shell, includling niv. # This would be useful behaviour if our CI seeds some cache. diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml index 8ac5228b8..41b4ff7cf 100644 --- a/.github/workflows/license.yml +++ b/.github/workflows/license.yml @@ -15,7 +15,7 @@ jobs: name: license-check:required runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - uses: EmbarkStudios/cargo-deny-action@3f4a782664881cf5725d0ffd23969fcce89fd868 # v1.6.3 with: command: check bans licenses sources diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1622877e6..de511b840 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,16 +29,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@150fca883cd4034361b621bd4e6a9d34e5143606 # v1.15.4 with: cache: false - name: Authenticate with crates.io id: auth - uses: rust-lang/crates-io-auth-action@v1 + uses: rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec # v1 # Crates are ordered by dependency: publish dependencies before dependents. # ic_principal has no workspace deps. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a909b872d..06db62bd4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: artifact_name: target/arm-unknown-linux-gnueabihf/release/didc asset_name: didc-arm32 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Install Rust toolchain run: rustup show active-toolchain || rustup toolchain install - name: Add arm target @@ -44,13 +44,13 @@ jobs: run: cargo build --package didc --release --locked - name: Cross build if: matrix.name == 'arm' - uses: actions-rs/cargo@v1 + uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # v1.0.3 with: use-cross: true command: build args: --package didc --target arm-unknown-linux-gnueabihf --release --locked - name: "Upload assets" - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ${{ matrix.asset_name }} path: ${{ matrix.artifact_name }} @@ -70,7 +70,7 @@ jobs: steps: - name: Get executable id: download - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: ${{ matrix.asset_name }} - name: Executable runs @@ -90,11 +90,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Get executable - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: ${{ matrix.asset_name }} - name: Upload binaries to release - uses: svenstaro/upload-release-action@v2 + uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # 2.11.5 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: didc @@ -105,9 +105,9 @@ jobs: name: Build and publish candid_ui canister runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '20' - name: Install Rust toolchain @@ -127,7 +127,7 @@ jobs: run: | cp tools/ui/target/wasm32-unknown-unknown/canister/didjs_opt.wasm candid_ui.wasm - name: Upload candid_ui to release - uses: svenstaro/upload-release-action@v2 + uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # 2.11.5 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: candid_ui.wasm diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fecd072e7..eedfff8a9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,9 +13,9 @@ jobs: rust: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Cache cargo build - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ~/.cargo/registry @@ -39,7 +39,7 @@ jobs: fuzzing: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Install cargo-fuzz run: | cargo install cargo-fuzz diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 1c312b843..534812e8f 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -15,7 +15,7 @@ jobs: build-didc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Build didc run: cargo build -p didc --release @@ -26,16 +26,16 @@ jobs: run: working-directory: ${{ env.WORKING_DIR }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 # TODO: use dfinity/ci-tools/actions/setup-pnpm once the repo has a standard structure with a package.json file in the root - name: Install pnpm - uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d # v3 with: package_json_file: ${{ env.WORKING_DIR }}/package.json - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version-file: ${{ env.WORKING_DIR }}/.node-version registry-url: "https://registry.npmjs.org" From bf4a997b74207eb8cfaf6b53ea7087eeb28a06db Mon Sep 17 00:00:00 2001 From: Adam Spofford <93943719+adamspofford-dfinity@users.noreply.github.com> Date: Tue, 5 May 2026 10:40:49 -0700 Subject: [PATCH 33/43] feat: Add schemars feature to ic_principal (and release 0.1.3) (#727) Co-authored-by: Linwei Shang --- Cargo.lock | 86 ++++++++++++++++++--- rust/bench/Cargo.lock | 2 +- rust/candid/fuzz/Cargo.lock | 8 +- rust/candid_parser/src/bindings/analysis.rs | 16 ++-- rust/ic_principal/Cargo.toml | 4 +- rust/ic_principal/src/lib.rs | 14 ++++ 6 files changed, 103 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0a09b737..26107e46c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,7 +218,7 @@ dependencies = [ "candid_derive 0.10.27", "candid_parser 0.3.1", "hex", - "ic_principal 0.1.2", + "ic_principal 0.1.3", "leb128", "num-bigint", "num-traits", @@ -542,6 +542,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.13.0" @@ -702,13 +708,14 @@ dependencies = [ [[package]] name = "ic_principal" -version = "0.1.2" +version = "0.1.3" dependencies = [ "arbitrary", "crc32fast", "data-encoding", "impls", "rangemap", + "schemars", "serde", "serde_cbor", "serde_json", @@ -1197,6 +1204,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.87", +] + [[package]] name = "regex" version = "1.10.5" @@ -1251,12 +1278,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - [[package]] name = "same-file" version = "1.0.6" @@ -1266,6 +1287,31 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "serde_derive_internals", + "syn 2.0.87", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1332,16 +1378,28 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.36", + "syn 2.0.87", +] + [[package]] name = "serde_json" -version = "1.0.121" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -1842,3 +1900,9 @@ name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index 9b15a6219..e67a9e090 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -507,7 +507,7 @@ checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" [[package]] name = "ic_principal" -version = "0.1.2" +version = "0.1.3" dependencies = [ "arbitrary", "crc32fast", diff --git a/rust/candid/fuzz/Cargo.lock b/rust/candid/fuzz/Cargo.lock index 59f1cd8bc..c046c15f3 100644 --- a/rust/candid/fuzz/Cargo.lock +++ b/rust/candid/fuzz/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "anyhow" @@ -66,7 +66,7 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "candid" -version = "0.10.8" +version = "0.10.27" dependencies = [ "anyhow", "binread", @@ -97,7 +97,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.6.6" +version = "0.10.27" dependencies = [ "lazy_static", "proc-macro2", @@ -190,7 +190,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "ic_principal" -version = "0.1.1" +version = "0.1.3" dependencies = [ "arbitrary", "crc32fast", diff --git a/rust/candid_parser/src/bindings/analysis.rs b/rust/candid_parser/src/bindings/analysis.rs index edd316bc7..2633c6f08 100644 --- a/rust/candid_parser/src/bindings/analysis.rs +++ b/rust/candid_parser/src/bindings/analysis.rs @@ -42,12 +42,10 @@ pub fn chase_type<'a>( ) -> Result<()> { use TypeInner::*; match t.as_ref() { - Var(id) => { - if seen.insert(id) { - let t = env.find_type(id)?; - chase_type(seen, res, env, t)?; - res.push(id); - } + Var(id) if seen.insert(id) => { + let t = env.find_type(id)?; + chase_type(seen, res, env, t)?; + res.push(id); } Opt(ty) | Vec(ty) => chase_type(seen, res, env, ty)?, Record(fs) | Variant(fs) => { @@ -147,10 +145,8 @@ pub fn infer_rec<'a>(_env: &'a TypeEnv, def_list: &'a [&'a str]) -> Result Result<()> { use TypeInner::*; match t.as_ref() { - Var(id) => { - if seen.insert(id) { - res.insert(id); - } + Var(id) if seen.insert(id) => { + res.insert(id); } Opt(ty) | Vec(ty) => go(seen, res, _env, ty)?, Record(fs) | Variant(fs) => { diff --git a/rust/ic_principal/Cargo.toml b/rust/ic_principal/Cargo.toml index 5f889810e..1d0a323fc 100644 --- a/rust/ic_principal/Cargo.toml +++ b/rust/ic_principal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ic_principal" -version = "0.1.2" +version = "0.1.3" authors = ["DFINITY Stiftung "] edition = "2021" rust-version.workspace = true @@ -19,6 +19,7 @@ arbitrary = { workspace = true, optional = true } crc32fast = { version = "1.3.0", optional = true } data-encoding = { version = "2.3.2", optional = true } rangemap = { version = "1.6.0", optional = true } +schemars = { version = "1.2.1", optional = true } serde = { workspace = true, optional = true } sha2 = { version = "0.10.1", optional = true } thiserror = { version = "1.0", optional = true } @@ -35,6 +36,7 @@ default = ['convert', 'self_authenticating', 'serde'] arbitrary = ['dep:arbitrary', 'serde'] convert = ['dep:crc32fast', 'dep:data-encoding', 'dep:thiserror'] self_authenticating = ['dep:sha2'] +schemars = ['dep:schemars', 'serde'] serde = ['dep:serde', 'convert'] rangemap = ['dep:rangemap'] diff --git a/rust/ic_principal/src/lib.rs b/rust/ic_principal/src/lib.rs index 7dfddfd5f..bc473fc5f 100644 --- a/rust/ic_principal/src/lib.rs +++ b/rust/ic_principal/src/lib.rs @@ -440,3 +440,17 @@ impl rangemap::StepLite for Principal { new } } + +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for Principal { + fn schema_name() -> std::borrow::Cow<'static, str> { + "Principal".into() + } + + fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + schemars::json_schema!({ + "type": "string", + "description": "ICP principal in text format." + }) + } +} From 578487d0fefde5a84183c25517cfa72eac0c437b Mon Sep 17 00:00:00 2001 From: Kamil Listopad Date: Thu, 7 May 2026 11:09:10 +0200 Subject: [PATCH 34/43] fix(candid_parser): emit `Float32` for Candid `float32` in Motoko binding (#728) ## Summary - Replace `panic!("float32 not supported in Motoko")` with `str("Float32")` in the Motoko binding generator. - Add a `float.did` test fixture covering `float32`/`float64` round-trip methods. ## Why Motoko added `Float32` in version 1.4.0 ([dfinity/motoko#5906](https://github.com/dfinity/motoko/pull/5906)), updating both the import and export IDL mappings. The spec (`design/IDL-Motoko.md`) is being updated in a companion PR. This brings `didc bind --target mo` in line with what `moc` already does when importing `.did` files directly. Concretely, any `.did` file using `float32` (e.g. the IC management canister interface) would previously cause `didc bind --target mo` to panic at runtime. --------- Co-authored-by: Cursor --- CHANGELOG.md | 7 ++++++ rust/candid_parser/src/bindings/motoko.rs | 2 +- rust/candid_parser/tests/assets/float.did | 5 ++++ rust/candid_parser/tests/assets/ok/float.d.ts | 11 +++++++++ rust/candid_parser/tests/assets/ok/float.did | 5 ++++ rust/candid_parser/tests/assets/ok/float.js | 8 +++++++ rust/candid_parser/tests/assets/ok/float.mo | 10 ++++++++ rust/candid_parser/tests/assets/ok/float.rs | 23 +++++++++++++++++++ 8 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 rust/candid_parser/tests/assets/float.did create mode 100644 rust/candid_parser/tests/assets/ok/float.d.ts create mode 100644 rust/candid_parser/tests/assets/ok/float.did create mode 100644 rust/candid_parser/tests/assets/ok/float.js create mode 100644 rust/candid_parser/tests/assets/ok/float.mo create mode 100644 rust/candid_parser/tests/assets/ok/float.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d65e14e96..042d169b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +### candid_parser + +* Bug fixes: + + Motoko binding: emit `Float32` for Candid `float32` instead of panicking. `float32` support was added to Motoko in version 1.4.0. + ## 2026-04-08 ### didc 0.6.1 diff --git a/rust/candid_parser/src/bindings/motoko.rs b/rust/candid_parser/src/bindings/motoko.rs index 6302fde39..326b7fc61 100644 --- a/rust/candid_parser/src/bindings/motoko.rs +++ b/rust/candid_parser/src/bindings/motoko.rs @@ -133,7 +133,7 @@ fn pp_ty(ty: &Type) -> RcDoc<'_> { Int16 => str("Int16"), Int32 => str("Int32"), Int64 => str("Int64"), - Float32 => panic!("float32 not supported in Motoko"), + Float32 => str("Float32"), Float64 => str("Float"), Text => str("Text"), Reserved => str("Any"), diff --git a/rust/candid_parser/tests/assets/float.did b/rust/candid_parser/tests/assets/float.did new file mode 100644 index 000000000..0bc5fef10 --- /dev/null +++ b/rust/candid_parser/tests/assets/float.did @@ -0,0 +1,5 @@ +service : { + to_f32 : (float64) -> (float32); + to_f64 : (float32) -> (float64); + identity32 : (float32) -> (float32); +} diff --git a/rust/candid_parser/tests/assets/ok/float.d.ts b/rust/candid_parser/tests/assets/ok/float.d.ts new file mode 100644 index 000000000..d30eecb3f --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.d.ts @@ -0,0 +1,11 @@ +import type { Principal } from '@icp-sdk/core/principal'; +import type { ActorMethod } from '@icp-sdk/core/agent'; +import type { IDL } from '@icp-sdk/core/candid'; + +export interface _SERVICE { + 'identity32' : ActorMethod<[number], number>, + 'to_f32' : ActorMethod<[number], number>, + 'to_f64' : ActorMethod<[number], number>, +} +export declare const idlFactory: IDL.InterfaceFactory; +export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/rust/candid_parser/tests/assets/ok/float.did b/rust/candid_parser/tests/assets/ok/float.did new file mode 100644 index 000000000..3b258dd2f --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.did @@ -0,0 +1,5 @@ +service : { + identity32 : (float32) -> (float32); + to_f32 : (float64) -> (float32); + to_f64 : (float32) -> (float64); +} diff --git a/rust/candid_parser/tests/assets/ok/float.js b/rust/candid_parser/tests/assets/ok/float.js new file mode 100644 index 000000000..f44891659 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.js @@ -0,0 +1,8 @@ +export const idlFactory = ({ IDL }) => { + return IDL.Service({ + 'identity32' : IDL.Func([IDL.Float32], [IDL.Float32], []), + 'to_f32' : IDL.Func([IDL.Float64], [IDL.Float32], []), + 'to_f64' : IDL.Func([IDL.Float32], [IDL.Float64], []), + }); +}; +export const init = ({ IDL }) => { return []; }; diff --git a/rust/candid_parser/tests/assets/ok/float.mo b/rust/candid_parser/tests/assets/ok/float.mo new file mode 100644 index 000000000..2ba13f2d1 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.mo @@ -0,0 +1,10 @@ +// This is a generated Motoko binding. +// Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. + +module { + public type Self = actor { + identity32 : shared Float32 -> async Float32; + to_f32 : shared Float -> async Float32; + to_f64 : shared Float32 -> async Float; + } +} diff --git a/rust/candid_parser/tests/assets/ok/float.rs b/rust/candid_parser/tests/assets/ok/float.rs new file mode 100644 index 000000000..8d2967b88 --- /dev/null +++ b/rust/candid_parser/tests/assets/ok/float.rs @@ -0,0 +1,23 @@ +// This is an experimental feature to generate Rust binding from Candid. +// You may want to manually adjust some of the types. +#![allow(dead_code, unused_imports)] +use candid::{self, CandidType, Deserialize, Principal}; +use ic_cdk::api::call::CallResult as Result; + + +pub struct Service(pub Principal); +impl Service { + pub async fn identity32(&self, arg0: &f32) -> Result<(f32,)> { + ic_cdk::call(self.0, "identity32", (arg0,)).await + } + pub async fn to_f32(&self, arg0: &f64) -> Result<(f32,)> { + ic_cdk::call(self.0, "to_f32", (arg0,)).await + } + pub async fn to_f64(&self, arg0: &f32) -> Result<(f64,)> { + ic_cdk::call(self.0, "to_f64", (arg0,)).await + } +} +/// Canister ID: `aaaaa-aa` +pub const CANISTER_ID : Principal = Principal::from_slice(&[]); +pub const service : Service = Service(CANISTER_ID); + From dd3d5ab98ecd27c122b4cbe4b946545ba9cd539d Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Wed, 20 May 2026 15:21:19 -0400 Subject: [PATCH 35/43] Release candid 0.10.28 and candid_parser 0.3.2 (#731) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Fix LEB128/SLEB128 fast path silently truncating `Nat`/`Int` values near the `u64`/`i64` boundary during decoding (ecc0c45) - Fix `Int::decode` truncating large magnitudes due to fast-path leakage (d08b8da) - Bump `candid` 0.10.27 → 0.10.28 and `candid_derive` to match - Cut `candid_parser` 0.3.2, which moves the previously unreleased Motoko `Float32` binding fix into a dated release - Update `CHANGELOG.md` with a 2026-05-20 entry covering both crates ## Test plan - [x] `cargo check -p candid -p candid_derive -p candid_parser` - [x] Reviewer to confirm changelog wording / release scope - [ ] Publish to crates.io after merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 10 +++- Cargo.lock | 14 +++--- rust/bench/Cargo.lock | 6 +-- rust/candid/Cargo.toml | 4 +- rust/candid/src/de.rs | 27 +++++------ rust/candid/src/types/number.rs | 6 ++- rust/candid/tests/serde.rs | 81 +++++++++++++++++++++++++++++++++ rust/candid_derive/Cargo.toml | 2 +- rust/candid_parser/Cargo.toml | 2 +- 9 files changed, 122 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 042d169b5..90e1e3a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ # Changelog -## Unreleased +## 2026-05-20 -### candid_parser +### Candid 0.10.28 + +* Bug fixes: + + Fix LEB128/SLEB128 fast path silently truncating `Nat`/`Int` values near the `u64`/`i64` boundary during decoding + + Fix `Int::decode` truncating large magnitudes due to fast-path leakage + +### candid_parser 0.3.2 * Bug fixes: + Motoko binding: emit `Float32` for Candid `float32` instead of panicking. `float32` support was added to Motoko in version 1.4.0. diff --git a/Cargo.lock b/Cargo.lock index 26107e46c..0b6118c39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,14 +209,14 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.27" +version = "0.10.28" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.27", - "candid_parser 0.3.1", + "candid_derive 0.10.28", + "candid_parser 0.3.2", "hex", "ic_principal 0.1.3", "leb128", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.27" +version = "0.10.28" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -276,11 +276,11 @@ dependencies = [ [[package]] name = "candid_parser" -version = "0.3.1" +version = "0.3.2" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.27", + "candid 0.10.28", "codespan-reporting", "console", "convert_case", @@ -482,7 +482,7 @@ name = "didc" version = "0.6.1" dependencies = [ "anyhow", - "candid_parser 0.3.1", + "candid_parser 0.3.2", "clap", "console", "hex", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index e67a9e090..99c0795f9 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.27" +version = "0.10.28" dependencies = [ "anyhow", "binread", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.27" +version = "0.10.28" dependencies = [ "lazy_static", "proc-macro2", @@ -187,7 +187,7 @@ dependencies = [ [[package]] name = "candid_parser" -version = "0.3.1" +version = "0.3.2" dependencies = [ "anyhow", "candid", diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index f5b0a8b4c..f5125e926 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.27" +version = "0.10.28" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.27" } +candid_derive = { path = "../candid_derive", version = "=0.10.28" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index 3471b323d..d457d9451 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -354,8 +354,12 @@ impl<'de> Deserializer<'de> { self.try_read_leb_u64()? .ok_or_else(|| Error::msg("LEB128 overflow")) } - /// Returns `Ok(None)` on overflow (value too large for u64), `Err` on I/O error (e.g. EOF). + /// Returns `Ok(None)` on overflow (value may not fit in u64), `Err` on I/O error (e.g. EOF). /// This lets callers fall through to a bignum path on overflow without swallowing real errors. + /// + /// The fast path covers up to 9 LEB128 bytes (values that fit in 63 bits, i.e. < 2^63). + /// Larger values bail to the bignum path, which keeps this hot loop simple — values needing + /// the 10th byte may or may not fit in u64, and the boundary check would just slow it down. #[inline] fn try_read_leb_u64(&mut self) -> Result> { let slice = self.input.get_ref(); @@ -369,22 +373,22 @@ impl<'de> Deserializer<'de> { } let byte = slice[pos]; pos += 1; - let low = (byte & 0x7f) as u64; - if shift < 64 { - result |= low << shift; - } + result |= ((byte & 0x7f) as u64) << shift; if byte & 0x80 == 0 { self.input.set_position(pos as u64); return Ok(Some(result)); } shift += 7; - if shift >= 70 { + if shift >= 63 { return Ok(None); } } } - /// Returns `Ok(None)` on overflow (value too large for i64), `Err` on I/O error (e.g. EOF). + /// Returns `Ok(None)` on overflow (value may not fit in i64), `Err` on I/O error (e.g. EOF). /// This lets callers fall through to a bignum path on overflow without swallowing real errors. + /// + /// The fast path covers up to 9 LEB128 bytes (values in roughly -2^62 .. 2^62). Larger + /// values bail to the bignum path, keeping this hot loop simple. #[inline] fn try_read_leb_i64(&mut self) -> Result> { let slice = self.input.get_ref(); @@ -399,19 +403,16 @@ impl<'de> Deserializer<'de> { } byte = slice[pos]; pos += 1; - let low = (byte & 0x7f) as i64; - if shift < 64 { - result |= low << shift; - } + result |= ((byte & 0x7f) as i64) << shift; shift += 7; if byte & 0x80 == 0 { break; } - if shift >= 70 { + if shift >= 63 { return Ok(None); } } - if shift < 64 && byte & 0x40 != 0 { + if byte & 0x40 != 0 { result |= !0i64 << shift; } self.input.set_position(pos as u64); diff --git a/rust/candid/src/types/number.rs b/rust/candid/src/types/number.rs index 14ba0b89a..64e3501dd 100644 --- a/rust/candid/src/types/number.rs +++ b/rust/candid/src/types/number.rs @@ -319,7 +319,11 @@ impl Int { let fits_i64 = if shift < 57 { true - } else if shift < 64 { + } else if shift < 64 && byte & 0x80 == 0 { + // Only the terminal byte can confirm the value fits in i64: + // if continuation is set, more bytes follow at shifts >= 64 + // and the value's high bits won't fit. Without this guard, + // `low_bits << shift` would silently truncate bits 1..6. let remaining_bits = 64 - shift; if (byte & 0x40) != 0 { (low_bits | !0x7f) >> (remaining_bits - 1) == -1 diff --git a/rust/candid/tests/serde.rs b/rust/candid/tests/serde.rs index 6ae16206a..39b6ef034 100644 --- a/rust/candid/tests/serde.rs +++ b/rust/candid/tests/serde.rs @@ -76,6 +76,87 @@ fn test_integer() { check_error(|| test_decode(&hex("4449444c00017c2a"), &42i64), "Int64"); } +// Regression for the v0.10.27 round-trip bug: the LEB128 fast path in +// `deserialize_nat`/`deserialize_int` silently truncated values that didn't +// fit in u64/i64 (e.g. `1 << 64` decoded as 0). The fast path now bails to +// the bignum decoder for any value spanning bit 63 or beyond. +#[test] +fn test_bignum_roundtrip_across_fast_path_boundary() { + use std::ops::Mul; + + // The minimal reproducer from the original bug report. + #[derive(CandidType, Deserialize, Debug, PartialEq)] + struct Foo { + amount: Option, + } + let foo = Foo { + amount: Some(Nat::from(1_u128 << 64)), + }; + let bytes = Encode!(&foo).unwrap(); + assert_eq!(Decode!(&bytes, Foo).unwrap(), foo); + + // Sweep Nat across the 63/64/65/... bit boundary plus a value past u128. + for k in 60..=68 { + for v in [(1_u128 << k) - 1, 1_u128 << k, (1_u128 << k) + 1] { + let n = Nat::from(v); + let bytes = Encode!(&n).unwrap(); + assert_eq!(Decode!(&bytes, Nat).unwrap(), n, "Nat roundtrip {v}"); + } + } + let big = Nat::from(u128::MAX).mul(Nat::from(7u32)); + assert_eq!(Decode!(&Encode!(&big).unwrap(), Nat).unwrap(), big); + + // Int boundary: values straddling i64 limits and a 65-bit value. + for v in [ + i64::MAX as i128 - 1, + i64::MAX as i128, + i64::MAX as i128 + 1, + i64::MIN as i128 - 1, + i64::MIN as i128, + i64::MIN as i128 + 1, + 1_i128 << 65, + -(1_i128 << 65), + ] { + let n = Int::from(v); + let bytes = Encode!(&n).unwrap(); + assert_eq!(Decode!(&bytes, Int).unwrap(), n, "Int roundtrip {v}"); + } +} + +// Regression for a pre-existing bug in `Int::decode`'s small-value fast +// path (#717): at shift=63 the `fits_i64` check treated a chunk whose +// non-data bits matched the sign-extension pattern as "fits", even when +// the byte's continuation bit was set. The shift `low_bits << 63` then +// silently truncated, and the resulting `i64` value was carried into the +// `BigInt` slow path as a corrupt seed — e.g. `i128::MAX` round-tripped +// to -1. +#[test] +fn test_int_decode_large_magnitude_roundtrip() { + use std::ops::Mul; + for v in [ + i128::MAX, + i128::MIN, + i128::MAX - 1, + i128::MIN + 1, + 1_i128 << 100, + -(1_i128 << 100), + (1_i128 << 126) - 1, + -(1_i128 << 126), + ] { + let n = Int::from(v); + let bytes = Encode!(&n).unwrap(); + assert_eq!(Decode!(&bytes, Int).unwrap(), n, "Int roundtrip {v}"); + } + // Beyond i128: ±(i128::MAX * 3). + let huge = Int::from(i128::MAX).mul(Int::from(3)); + assert_eq!(Decode!(&Encode!(&huge).unwrap(), Int).unwrap(), huge); + let huge_neg = Int::from(i128::MIN).mul(Int::from(3)); + assert_eq!( + Decode!(&Encode!(&huge_neg).unwrap(), Int).unwrap(), + huge_neg + ); +} + #[test] fn test_fixed_number() { all_check(42u8, "4449444c00017b2a"); diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index efd23f980..71b040381 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.27" +version = "0.10.28" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] diff --git a/rust/candid_parser/Cargo.toml b/rust/candid_parser/Cargo.toml index dbb4edffe..ebbd5cd19 100644 --- a/rust/candid_parser/Cargo.toml +++ b/rust/candid_parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid_parser" -version = "0.3.1" +version = "0.3.2" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] From f97db285a05ef378588f8c36728f58de00547f0f Mon Sep 17 00:00:00 2001 From: mraszyk <31483726+mraszyk@users.noreply.github.com> Date: Wed, 27 May 2026 23:17:37 +0200 Subject: [PATCH 36/43] fix: text_fast_path leakage between nested maps + release candid 0.10.29 (#732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Fix: `text_fast_path` leaks into nested maps with non-text keys ### Problem Decoding a `BTreeMap` where `V` is an enum containing a `BTreeMap` with a non-`String` key type fails with a subtyping error: Type mismatch at de.rs:1380 The error fires in `deserialize_enum` when `expect_type` is not a `TypeInner::Variant`, even though the wire type correctly encodes the key as a Candid variant. ### Root cause `deserialize_map` has a fast path for `BTreeMap`: when the key type is `Text`, it sets `self.text_fast_path = true` on the deserializer **globally** before entering `visitor.visit_map(...)`. This flag is then visible to any code that runs inside the visitor — including `next_key_seed` of a **nested** inner map. `next_key_seed` for `Style::Map` checked `self.de.text_fast_path` to decide whether to skip updating `expect_type`/`wire_type` for the key: ```rust if !self.de.text_fast_path { // ← reads global flag, not this map's flag self.de.expect_type = expect.0.clone(); self.de.wire_type = wire.0.clone(); } ``` When the inner map has non-text keys (e.g. an enum), the outer map's text_fast_path = true causes next_key_seed to skip the type update. The deserializer then calls deserialize_enum with a stale expect_type (whatever was left over from the enclosing context — a vec type, a record, etc.), which is not TypeInner::Variant, triggering the crash. ### Fix Add key_text_fast: bool to Style::Map so that each map compound carries its own fast-path decision, independent of any enclosing map's state. next_key_seed now: 1. uses style.key_text_fast (not the global flag) to decide whether the per-entry add_cost(4) was pre-paid, and 2. sets self.de.text_fast_path = key_text_fast immediately before calling the key deserializer, so deserialize_str's fast path is engaged only when this map's key is actually Text. No changes to next_value_seed are required: after next_key_seed sets text_fast_path correctly, next_value_seed's any_fast check (which reads self.de.text_fast_path) naturally reflects the current map's key type. ### Regression test test_nested_map_non_text_key in tests/serde.rs encodes and round-trips: ```rust BTreeMap "fun" => Value::Fun(BTreeMap) // inner map with enum key "lit" => Value::Lit(String) ``` This test panicked with "Type mismatch" before the fix and passes after. --------- Co-authored-by: Linwei Shang Co-authored-by: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 7 +++++++ Cargo.lock | 8 +++---- rust/bench/Cargo.lock | 4 ++-- rust/candid/Cargo.toml | 4 ++-- rust/candid/src/de.rs | 21 +++++++++++++++---- rust/candid/tests/serde.rs | 39 +++++++++++++++++++++++++++++++++++ rust/candid_derive/Cargo.toml | 2 +- 7 files changed, 72 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e1e3a15..3efcfa0e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2026-05-27 + +### Candid 0.10.29 + +* Bug fixes: + + Fix `text_fast_path` leakage between nested maps: an inner map with non-text keys would fail with a "Type mismatch" error when enclosed in an outer map with text keys + ## 2026-05-20 ### Candid 0.10.28 diff --git a/Cargo.lock b/Cargo.lock index 0b6118c39..2529594e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,13 +209,13 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.28" +version = "0.10.29" dependencies = [ "anyhow", "bincode", "binread", "byteorder", - "candid_derive 0.10.28", + "candid_derive 0.10.29", "candid_parser 0.3.2", "hex", "ic_principal 0.1.3", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.28" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2 1.0.86", @@ -280,7 +280,7 @@ version = "0.3.2" dependencies = [ "anyhow", "arbitrary", - "candid 0.10.28", + "candid 0.10.29", "codespan-reporting", "console", "convert_case", diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index 99c0795f9..cd34cf046 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -156,7 +156,7 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.28" +version = "0.10.29" dependencies = [ "anyhow", "binread", @@ -177,7 +177,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.28" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2", diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index f5125e926..f46667c74 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid" # sync with the version in `candid_derive/Cargo.toml` -version = "0.10.28" +version = "0.10.29" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] @@ -16,7 +16,7 @@ keywords = ["internet-computer", "idl", "candid", "dfinity"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] [dependencies] -candid_derive = { path = "../candid_derive", version = "=0.10.28" } +candid_derive = { path = "../candid_derive", version = "=0.10.29" } ic_principal = { path = "../ic_principal", version = "0.1.0" } binread = { version = "2.2", features = ["debug_template"] } byteorder = "1.5.0" diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index d457d9451..f18a334be 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -1280,7 +1280,12 @@ impl<'de> de::Deserializer<'de> for &mut Deserializer<'de> { let result = visitor.visit_map(Compound::new( self, - Style::Map { len, expect, wire }, + Style::Map { + len, + expect, + wire, + key_text_fast, + }, )); self.text_fast_path = false; #[cfg(feature = "bignum")] @@ -1426,6 +1431,7 @@ enum Style { len: usize, expect: (Type, Type), wire: (Type, Type), + key_text_fast: bool, }, } @@ -1701,19 +1707,26 @@ impl<'de> de::MapAccess<'de> for Compound<'_, 'de> { ref mut len, ref expect, ref wire, + key_text_fast, } => { if *len == 0 { return Ok(None); } *len -= 1; #[cfg(feature = "bignum")] - let any_fast = self.de.text_fast_path || self.de.bignum_vec_fast_path.is_some(); + let any_fast = key_text_fast || self.de.bignum_vec_fast_path.is_some(); #[cfg(not(feature = "bignum"))] - let any_fast = self.de.text_fast_path; + let any_fast = key_text_fast; if !any_fast { self.de.add_cost(4)?; } - if !self.de.text_fast_path { + // Always set text_fast_path based on THIS map's key type. The global + // text_fast_path may be true from an enclosing map with text keys; using + // it directly would skip setting expect_type/wire_type for non-text keys + // of this (inner) map, leading to a "Type mismatch" when deserializing + // those keys. + self.de.text_fast_path = key_text_fast; + if !key_text_fast { self.de.expect_type = expect.0.clone(); self.de.wire_type = wire.0.clone(); } diff --git a/rust/candid/tests/serde.rs b/rust/candid/tests/serde.rs index 39b6ef034..c521fb0a9 100644 --- a/rust/candid/tests/serde.rs +++ b/rust/candid/tests/serde.rs @@ -796,6 +796,45 @@ fn test_vector() { ); } +/// Regression test: decoding a `BTreeMap` where `V` is an enum containing +/// a `BTreeMap` with a non-text enum key used to fail with "Type mismatch". +/// +/// The bug was that `text_fast_path`, set to `true` by the outer string-keyed map's +/// `deserialize_map`, leaked into the inner map's `next_key_seed`. There it +/// suppressed the update of `expect_type`/`wire_type` for the inner key, so that +/// `deserialize_enum` on the inner key saw a stale (non-variant) `expect_type` and +/// raised a subtyping error at `de.rs:1380`. +#[test] +fn test_nested_map_non_text_key() { + use std::collections::BTreeMap; + + #[derive(CandidType, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + enum MapKey { + A, + B, + } + + #[derive(CandidType, Deserialize, Debug, Clone, PartialEq)] + enum Value { + Fun(BTreeMap), + Lit(String), + } + + let mut inner: BTreeMap = BTreeMap::new(); + inner.insert(MapKey::A, 1); + inner.insert(MapKey::B, 2); + + let mut outer: BTreeMap = BTreeMap::new(); + outer.insert("fun".to_string(), Value::Fun(inner)); + outer.insert("lit".to_string(), Value::Lit("hello".to_string())); + + // encode → decode must round-trip without "Type mismatch" panic + let bytes = encode_one(&outer).unwrap(); + let config = get_config(); + let decoded = decode_one_with_config::>(&bytes, &config).unwrap(); + assert_eq!(outer, decoded); +} + #[test] fn test_collection() { use std::collections::{BTreeMap, BTreeSet, HashMap}; diff --git a/rust/candid_derive/Cargo.toml b/rust/candid_derive/Cargo.toml index 71b040381..6cc239299 100644 --- a/rust/candid_derive/Cargo.toml +++ b/rust/candid_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "candid_derive" # sync with the version in `candid/Cargo.toml` -version = "0.10.28" +version = "0.10.29" edition = "2021" rust-version.workspace = true authors = ["DFINITY Team"] From 96dbb62db78c4b8a3b3d5b189959a472209f6efe Mon Sep 17 00:00:00 2001 From: raymondk Date: Tue, 16 Jun 2026 11:37:08 -0400 Subject: [PATCH 37/43] chore(ui): use icp-cli for the candid ui (#734) Make the candid ui deployable with icp-cli --- tools/ui/.gitignore | 9 +- tools/ui/.icp/data/mappings/ic.ids.json | 3 + tools/ui/Cargo.lock | 6 +- tools/ui/README.md | 20 +- tools/ui/canister_ids.json | 5 - tools/ui/dfx.json | 20 - tools/ui/icp.yaml | 11 + tools/ui/{src/candid.html => index.html} | 1 + tools/ui/package-lock.json | 5923 +++++++--------------- tools/ui/package.json | 16 +- tools/ui/{src => public}/favicon.ico | Bin tools/ui/vite.config.ts | 34 + tools/ui/webpack.config.js | 116 - 13 files changed, 1861 insertions(+), 4303 deletions(-) create mode 100644 tools/ui/.icp/data/mappings/ic.ids.json delete mode 100644 tools/ui/canister_ids.json delete mode 100644 tools/ui/dfx.json create mode 100644 tools/ui/icp.yaml rename tools/ui/{src/candid.html => index.html} (99%) rename tools/ui/{src => public}/favicon.ico (100%) create mode 100644 tools/ui/vite.config.ts delete mode 100644 tools/ui/webpack.config.js diff --git a/tools/ui/.gitignore b/tools/ui/.gitignore index b95879510..11f5b07bf 100644 --- a/tools/ui/.gitignore +++ b/tools/ui/.gitignore @@ -7,10 +7,15 @@ .DS_Store **/.DS_Store -# dfx temporary files -.dfx/ +# icp-cli ephemeral state (local network + build cache). +# Persistent data such as mainnet canister id mappings lives in .icp/data/ +# and IS committed. +.icp/cache/ # frontend code node_modules/ dist/ ts-out/ + +.dfx +.icp/cache diff --git a/tools/ui/.icp/data/mappings/ic.ids.json b/tools/ui/.icp/data/mappings/ic.ids.json new file mode 100644 index 000000000..18e5489dd --- /dev/null +++ b/tools/ui/.icp/data/mappings/ic.ids.json @@ -0,0 +1,3 @@ +{ + "didjs": "a4gq6-oaaaa-aaaab-qaa4q-cai" +} diff --git a/tools/ui/Cargo.lock b/tools/ui/Cargo.lock index 27cff3e08..9a063c590 100644 --- a/tools/ui/Cargo.lock +++ b/tools/ui/Cargo.lock @@ -132,7 +132,7 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "candid" -version = "0.10.20" +version = "0.10.29" dependencies = [ "anyhow", "binread", @@ -153,7 +153,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.20" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2", @@ -465,7 +465,7 @@ checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" [[package]] name = "ic_principal" -version = "0.1.1" +version = "0.1.3" dependencies = [ "arbitrary", "crc32fast", diff --git a/tools/ui/README.md b/tools/ui/README.md index a648abe7b..40c416170 100644 --- a/tools/ui/README.md +++ b/tools/ui/README.md @@ -7,17 +7,27 @@ The frontend `ui` fetches the Candid interface from the running canister (curren ## Build -You need `dfx`, `cargo`, `npm` and `wasm-opt` for building the canister. +You need [`icp`](https://cli.internetcomputer.org), `cargo`, `npm`, `wasm-opt` and +`ic-wasm` for building the canister. The frontend is bundled with [Vite](https://vite.dev). ```bash cd ui/ npm install -dfx start --background -dfx canister create --all -dfx build -dfx canister install --all +icp network start -d # start a local replica in the background +icp deploy # build + deploy the didjs canister locally ``` +To deploy to mainnet (the `ic` environment), which targets the existing +`a4gq6-oaaaa-aaaab-qaa4q-cai` canister: + +```bash +icp deploy -e ic +``` + +Project configuration lives in `icp.yaml`. Canister id mappings are stored under +`.icp/`: the mainnet mapping (`.icp/data/mappings/ic.ids.json`) is committed, while +local replica state under `.icp/cache/` is ephemeral and git-ignored. + ## Usage Open the `ui` canister in a browser and supply the id parameter in the URL to render a specific canister, diff --git a/tools/ui/canister_ids.json b/tools/ui/canister_ids.json deleted file mode 100644 index 016284e12..000000000 --- a/tools/ui/canister_ids.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "didjs": { - "ic": "a4gq6-oaaaa-aaaab-qaa4q-cai" - } -} \ No newline at end of file diff --git a/tools/ui/dfx.json b/tools/ui/dfx.json deleted file mode 100644 index 9125fc637..000000000 --- a/tools/ui/dfx.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "canisters": { - "didjs": { - "type": "custom", - "candid": "src/didjs/didjs.did", - "wasm": "target/wasm32-unknown-unknown/canister/didjs.wasm", - "build": [ - "npm run build", - "cargo build --target wasm32-unknown-unknown --profile canister --package didjs", - "wasm-opt --strip-debug -Oz target/wasm32-unknown-unknown/canister/didjs.wasm -o target/wasm32-unknown-unknown/canister/didjs_opt.wasm" - ], - "metadata": [ - { - "name": "candid:service" - } - ] - } - }, - "version": 1 -} diff --git a/tools/ui/icp.yaml b/tools/ui/icp.yaml new file mode 100644 index 000000000..d00091fde --- /dev/null +++ b/tools/ui/icp.yaml @@ -0,0 +1,11 @@ +canisters: + - name: didjs + build: + steps: + - type: script + commands: + - npm run build + - cargo build --target wasm32-unknown-unknown --profile canister --package didjs + - wasm-opt --strip-debug -Oz target/wasm32-unknown-unknown/canister/didjs.wasm -o target/wasm32-unknown-unknown/canister/didjs_opt.wasm + - ic-wasm target/wasm32-unknown-unknown/canister/didjs_opt.wasm -o target/wasm32-unknown-unknown/canister/didjs_opt.wasm metadata candid:service -f src/didjs/didjs.did -v public + - cp target/wasm32-unknown-unknown/canister/didjs_opt.wasm "$ICP_WASM_OUTPUT_PATH" diff --git a/tools/ui/src/candid.html b/tools/ui/index.html similarity index 99% rename from tools/ui/src/candid.html rename to tools/ui/index.html index cd681f3de..12fb4aac2 100644 --- a/tools/ui/src/candid.html +++ b/tools/ui/index.html @@ -137,5 +137,6 @@

Candid UI

outputButton.click(); } + diff --git a/tools/ui/package-lock.json b/tools/ui/package-lock.json index effe61dea..c7f3dd1ef 100644 --- a/tools/ui/package-lock.json +++ b/tools/ui/package-lock.json @@ -13,17 +13,9 @@ "@dfinity/candid": "2.1.3", "@dfinity/identity": "2.1.3", "@dfinity/principal": "2.1.3", - "buffer": "6.0.3", - "copy-webpack-plugin": "^12.0.2", - "css-loader": "^7.1.2", - "html-webpack-plugin": "^5.6.0", - "style-loader": "^4.0.0", - "terser-webpack-plugin": "^5.3.10", - "ts-loader": "^9.5.1", - "tsconfig-paths-webpack-plugin": "^4.1.0", "typescript": "^5.6.2", - "webpack": "5.95.0", - "webpack-cli": "5.1.4" + "vite": "^6.0.0", + "vite-plugin-css-injected-by-js": "^3.5.2" } }, "../agent-js/packages/agent": { @@ -34,6 +26,7 @@ "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-2.1.3.tgz", "integrity": "sha512-4XmqhFR3GQSUrmx7lMFx7DyHEhFkM6nz4O9FeYJ/WpkmPe8tulKaAfgWbWdTSCjbd8meCgKVHo+QYj+JHXagcw==", "dev": true, + "peer": true, "dependencies": { "@noble/curves": "^1.4.0", "@noble/hashes": "^1.3.1", @@ -66,6 +59,7 @@ "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-2.1.3.tgz", "integrity": "sha512-Asn7AfydLhhk7E5z9oW+5UL6ne11gxFlYTxHuhrIc7FdqYlM5Flcq1Wfg9EzRa6Btdol3w58Bcph7Brwh1bcIQ==", "dev": true, + "peer": true, "peerDependencies": { "@dfinity/principal": "^2.1.3" } @@ -75,6 +69,7 @@ "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-2.1.3.tgz", "integrity": "sha512-qII0V91S1YeIz5/XRHomwrUhTME+C3oqdTnb99tBitXA2Gq6LU2JaCLbKbN7ehhSyW6EjO4tySJxANz6hYENcQ==", "dev": true, + "peer": true, "dependencies": { "@noble/curves": "^1.2.0", "@noble/hashes": "^1.3.1", @@ -91,17 +86,451 @@ "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-2.1.3.tgz", "integrity": "sha512-HtiAfZcs+ToPYFepVJdFlorIfPA56KzC6J97ZuH2lGNMTAfJA+NEBzLe476B4wVCAwZ0TiGJ27J4ks9O79DFEg==", "dev": true, + "peer": true, "dependencies": { "@noble/hashes": "^1.3.1" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", - "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10.0.0" + "node": ">=18" } }, "node_modules/@jridgewell/gen-mapping": { @@ -109,6 +538,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "optional": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -123,6 +553,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "optional": true, "engines": { "node": ">=6.0.0" } @@ -132,6 +563,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "optional": true, "engines": { "node": ">=6.0.0" } @@ -141,6 +573,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, + "optional": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -150,13 +583,15 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "dev": true, + "optional": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "optional": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -189,47 +624,11 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@peculiar/asn1-schema": { "version": "2.3.13", "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", "dev": true, - "peer": true, "dependencies": { "asn1js": "^3.0.5", "pvtsutils": "^1.3.5", @@ -241,7 +640,6 @@ "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", "dev": true, - "peer": true, "dependencies": { "tslib": "^2.0.0" }, @@ -266,359 +664,368 @@ "node": ">=10.12.0" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@types/node": { - "version": "20.3.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz", - "integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==", - "dev": true + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@xtuc/long": "4.2.2" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" + ], "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" + ], "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/acorn": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", - "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], "dev": true, - "peerDependencies": { - "acorn": "^8" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "license": "MIT" }, "node_modules/asn1js": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", "dev": true, - "peer": true, "dependencies": { "pvtsutils": "^1.3.2", "pvutils": "^1.1.3", @@ -666,12 +1073,6 @@ "node": "*" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, "node_modules/borc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", @@ -714,50 +1115,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -786,3656 +1143,1241 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "optional": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/delimit-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", + "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=", "dev": true }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001664", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", - "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "type": "github", + "url": "https://github.com/sponsors/feross" }, { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + "type": "patreon", + "url": "https://www.patreon.com/feross" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "consulting", + "url": "https://feross.org/support" } ] }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/iso-url": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", + "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/json-text-sequence": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", + "integrity": "sha1-py8hfcSvxGKf/1/rME3BvVGi89I=", "dev": true, "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" + "delimit-stream": "0.1.0" } }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, - "dependencies": { - "source-map": "~0.6.0" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">= 10.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "license": "ISC" }, - "node_modules/copy-webpack-plugin": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", - "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, "dependencies": { - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.1", - "globby": "^14.0.0", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2" + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" + "node": "^10 || ^12 || >=14" } }, - "node_modules/copy-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "tslib": "^2.6.1" } }, - "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/copy-webpack-plugin/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">= 6" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "@types/estree": "1.0.9" }, - "engines": { - "node": ">= 8" + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" } }, - "node_modules/css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.27.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "webpack": { - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } - } + ] }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } + "node_modules/simple-cbor": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", + "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==", + "dev": true }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "optional": true, "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "node": ">=0.10.0" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/delimit-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", - "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=", - "dev": true - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "optional": true, "dependencies": { - "utila": "~0.4" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "safe-buffer": "~5.2.0" } }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, + "license": "MIT", "dependencies": { - "domelementtype": "^2.2.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">= 4" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" + "license": "MIT", + "engines": { + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.29", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", - "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", - "dev": true - }, - "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": ">=10.13.0" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, + "peer": true, "bin": { - "envinfo": "dist/cli.js" + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4" + "node": ">=14.17" } }, - "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": ">=4.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "node_modules/vite-plugin-css-injected-by-js": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.2.tgz", + "integrity": "sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ==", "dev": true, - "engines": { - "node": ">=4.0" + "license": "MIT", + "peerDependencies": { + "vite": ">2.0.0-0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", - "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob-parent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.1.tgz", - "integrity": "sha512-kEVjS71mQazDBHKcsq4E9u/vUzaLcw1A8EtUeydawvIWQCJM0qQ08G1H7/XTjFUulla6XQiDOG6MXSaG0HDKog==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", - "dev": true, - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dev": true, - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", - "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", - "dev": true, - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.20.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/iso-url": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", - "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-text-sequence": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", - "integrity": "sha1-py8hfcSvxGKf/1/rME3BvVGi89I=", - "dev": true, - "dependencies": { - "delimit-stream": "0.1.0" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", - "dev": true, - "dependencies": { - "mime-db": "1.49.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pvtsutils": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", - "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.6.1" - } - }, - "node_modules/pvutils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", - "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dev": true, - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/simple-cbor": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", - "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==", - "dev": true - }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/style-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", - "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", - "dev": true, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.27.0" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.34.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", - "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-loader": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", - "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "dev": true - }, - "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true - }, - "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webcrypto-core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", - "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", - "dev": true, - "peer": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.3.13", - "@peculiar/json-schema": "^1.1.12", - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.7.0" - } - }, - "node_modules/webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack/node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - } - }, - "dependencies": { - "@dfinity/agent": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-2.1.3.tgz", - "integrity": "sha512-4XmqhFR3GQSUrmx7lMFx7DyHEhFkM6nz4O9FeYJ/WpkmPe8tulKaAfgWbWdTSCjbd8meCgKVHo+QYj+JHXagcw==", - "dev": true, - "requires": { - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.3.1", - "base64-arraybuffer": "^0.2.0", - "borc": "^2.1.1", - "buffer": "^6.0.3", - "simple-cbor": "^0.4.1" - } - }, - "@dfinity/auth-client": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/auth-client/-/auth-client-2.1.3.tgz", - "integrity": "sha512-6lxv7w8FWSnu5FxMa75O1lddUQJWDDMgJCG1DeYM4+08dWy0TxV/CeEo2uInAdqwlZECgQYB3yQEZKUNkbA6OQ==", - "dev": true, - "requires": { - "idb": "^7.0.2" - } - }, - "@dfinity/candid": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-2.1.3.tgz", - "integrity": "sha512-Asn7AfydLhhk7E5z9oW+5UL6ne11gxFlYTxHuhrIc7FdqYlM5Flcq1Wfg9EzRa6Btdol3w58Bcph7Brwh1bcIQ==", - "dev": true, - "requires": {} - }, - "@dfinity/identity": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-2.1.3.tgz", - "integrity": "sha512-qII0V91S1YeIz5/XRHomwrUhTME+C3oqdTnb99tBitXA2Gq6LU2JaCLbKbN7ehhSyW6EjO4tySJxANz6hYENcQ==", - "dev": true, - "requires": { - "@noble/curves": "^1.2.0", - "@noble/hashes": "^1.3.1", - "borc": "^2.1.1" - } - }, - "@dfinity/principal": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-2.1.3.tgz", - "integrity": "sha512-HtiAfZcs+ToPYFepVJdFlorIfPA56KzC6J97ZuH2lGNMTAfJA+NEBzLe476B4wVCAwZ0TiGJ27J4ks9O79DFEg==", - "dev": true, - "requires": { - "@noble/hashes": "^1.3.1" - } - }, - "@discoveryjs/json-ext": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", - "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", - "dev": true - }, - "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@noble/curves": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", - "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", - "dev": true, - "requires": { - "@noble/hashes": "1.5.0" - } - }, - "@noble/hashes": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", - "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", - "dev": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@peculiar/asn1-schema": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", - "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", - "dev": true, - "peer": true, - "requires": { - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2" - } - }, - "@peculiar/json-schema": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", - "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", - "dev": true, - "peer": true, - "requires": { - "tslib": "^2.0.0" - } - }, - "@peculiar/webcrypto": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", - "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", - "dev": true, - "peer": true, - "requires": { - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/json-schema": "^1.1.12", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2", - "webcrypto-core": "^1.8.0" - } - }, - "@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "dev": true - }, - "@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true - }, - "@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/node": { - "version": "20.3.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz", - "integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==", - "dev": true - }, - "@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.12.1", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "dev": true, - "requires": {} - }, - "@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "dev": true, - "requires": {} - }, - "@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "dev": true, - "requires": {} - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "acorn": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", - "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", - "dev": true - }, - "acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "requires": { - "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "asn1js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", - "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "peer": true, - "requires": { - "pvtsutils": "^1.3.2", - "pvutils": "^1.1.3", - "tslib": "^2.4.0" - } - }, - "base64-arraybuffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", - "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bignumber.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", - "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "borc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", - "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", - "dev": true, - "requires": { - "bignumber.js": "^9.0.0", - "buffer": "^5.5.0", - "commander": "^2.15.0", - "ieee754": "^1.1.13", - "iso-url": "~0.4.7", - "json-text-sequence": "~0.1.0", - "readable-stream": "^3.6.0" + "engines": { + "node": ">=12" }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "caniuse-lite": { - "version": "1.0.30001664", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", - "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/webcrypto-core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", + "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dev": true, - "requires": { - "source-map": "~0.6.0" + "@peculiar/asn1-schema": "^2.3.13", + "@peculiar/json-schema": "^1.1.12", + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.7.0" } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + } + }, + "dependencies": { + "@dfinity/agent": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-2.1.3.tgz", + "integrity": "sha512-4XmqhFR3GQSUrmx7lMFx7DyHEhFkM6nz4O9FeYJ/WpkmPe8tulKaAfgWbWdTSCjbd8meCgKVHo+QYj+JHXagcw==", "dev": true, + "peer": true, "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "base64-arraybuffer": "^0.2.0", + "borc": "^2.1.1", + "buffer": "^6.0.3", + "simple-cbor": "^0.4.1" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "@dfinity/auth-client": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@dfinity/auth-client/-/auth-client-2.1.3.tgz", + "integrity": "sha512-6lxv7w8FWSnu5FxMa75O1lddUQJWDDMgJCG1DeYM4+08dWy0TxV/CeEo2uInAdqwlZECgQYB3yQEZKUNkbA6OQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "idb": "^7.0.2" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "copy-webpack-plugin": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", - "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "@dfinity/candid": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-2.1.3.tgz", + "integrity": "sha512-Asn7AfydLhhk7E5z9oW+5UL6ne11gxFlYTxHuhrIc7FdqYlM5Flcq1Wfg9EzRa6Btdol3w58Bcph7Brwh1bcIQ==", "dev": true, - "requires": { - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.1", - "globby": "^14.0.0", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2" - }, - "dependencies": { - "ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - } - } - } + "peer": true, + "requires": {} }, - "cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "@dfinity/identity": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-2.1.3.tgz", + "integrity": "sha512-qII0V91S1YeIz5/XRHomwrUhTME+C3oqdTnb99tBitXA2Gq6LU2JaCLbKbN7ehhSyW6EjO4tySJxANz6hYENcQ==", "dev": true, + "peer": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "@noble/curves": "^1.2.0", + "@noble/hashes": "^1.3.1", + "borc": "^2.1.1" } }, - "css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "@dfinity/principal": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-2.1.3.tgz", + "integrity": "sha512-HtiAfZcs+ToPYFepVJdFlorIfPA56KzC6J97ZuH2lGNMTAfJA+NEBzLe476B4wVCAwZ0TiGJ27J4ks9O79DFEg==", "dev": true, + "peer": true, "requires": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" + "@noble/hashes": "^1.3.1" } }, - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } + "optional": true }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true + "@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "dev": true, + "optional": true }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true + "@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "dev": true, + "optional": true }, - "delimit-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", - "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=", - "dev": true + "@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "dev": true, + "optional": true }, - "dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "dev": true, - "requires": { - "utila": "~0.4" - } + "optional": true }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } + "optional": true }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true + "@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "dev": true, + "optional": true }, - "domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } + "optional": true }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } + "optional": true }, - "dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } + "optional": true }, - "electron-to-chromium": { - "version": "1.5.29", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", - "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", - "dev": true + "@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "dev": true, + "optional": true }, - "enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } + "optional": true }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true + "@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "dev": true, + "optional": true }, - "envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", - "dev": true + "@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "dev": true, + "optional": true }, - "es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "dev": true + "@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "dev": true, + "optional": true }, - "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true + "@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "dev": true, + "optional": true }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } + "optional": true }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } + "optional": true }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "dev": true, + "optional": true }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "dev": true, + "optional": true }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "dev": true, + "optional": true }, - "fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } + "optional": true }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "dev": true, + "optional": true }, - "fast-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", - "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", - "dev": true + "@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "dev": true, + "optional": true }, - "fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", - "dev": true + "@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "dev": true, + "optional": true }, - "fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "dev": true, - "requires": { - "reusify": "^1.0.4" - } + "optional": true }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "optional": true, "requires": { - "to-regex-range": "^5.0.1" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } + "optional": true }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "optional": true }, - "glob-parent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.1.tgz", - "integrity": "sha512-kEVjS71mQazDBHKcsq4E9u/vUzaLcw1A8EtUeydawvIWQCJM0qQ08G1H7/XTjFUulla6XQiDOG6MXSaG0HDKog==", + "@jridgewell/source-map": { + "version": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, + "optional": true, "requires": { - "is-glob": "^4.0.1" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "optional": true }, - "globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "optional": true, "requires": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "@noble/curves": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", "dev": true, "requires": { - "function-bind": "^1.1.2" + "@noble/hashes": "1.5.0" } }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", "dev": true }, - "html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "@peculiar/asn1-schema": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", + "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", "dev": true, "requires": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "dependencies": { - "commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true - } + "asn1js": "^3.0.5", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2" } }, - "html-webpack-plugin": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", - "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", "dev": true, "requires": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" + "tslib": "^2.0.0" } }, - "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "@peculiar/webcrypto": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", + "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", "dev": true, + "peer": true, "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" + "@peculiar/asn1-schema": "^2.3.8", + "@peculiar/json-schema": "^1.1.12", + "pvtsutils": "^1.3.5", + "tslib": "^2.6.2", + "webcrypto-core": "^1.8.0" } }, - "icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", "dev": true, - "requires": {} - }, - "idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true + "optional": true }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } + "optional": true }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "dev": true, + "optional": true }, - "interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true + "@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "dev": true, + "optional": true }, - "is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", "dev": true, - "requires": { - "hasown": "^2.0.2" - } + "optional": true }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "dev": true, + "optional": true }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } + "optional": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "dev": true, + "optional": true }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", "dev": true, - "requires": { - "isobject": "^3.0.1" - } + "optional": true }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "dev": true, + "optional": true }, - "iso-url": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", - "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", - "dev": true + "@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "dev": true, + "optional": true }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "dev": true, + "optional": true }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } + "optional": true }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "dev": true, + "optional": true }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "dev": true, + "optional": true }, - "json-text-sequence": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", - "integrity": "sha1-py8hfcSvxGKf/1/rME3BvVGi89I=", + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", "dev": true, - "requires": { - "delimit-stream": "0.1.0" - } + "optional": true }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "dev": true, + "optional": true }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true + "@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "dev": true, + "optional": true }, - "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true + "@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "dev": true, + "optional": true }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", "dev": true, - "requires": { - "p-locate": "^4.1.0" - } + "optional": true }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "dev": true, + "optional": true }, - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", "dev": true, - "requires": { - "tslib": "^2.0.3" - } + "optional": true }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "dev": true, + "optional": true }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "dev": true, + "optional": true }, - "micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", "dev": true, - "requires": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - } + "optional": true }, - "mime-db": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", - "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true }, - "mime-types": { - "version": "2.1.32", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", - "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", "dev": true, "requires": { - "mime-db": "1.49.0" + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" } }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "base64-arraybuffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", + "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", "dev": true }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", "dev": true }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "borc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", + "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "bignumber.js": "^9.0.0", + "buffer": "^5.5.0", + "commander": "^2.15.0", + "ieee754": "^1.1.13", + "iso-url": "~0.4.7", + "json-text-sequence": "~0.1.0", + "readable-stream": "^3.6.0" }, "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } } } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "requires": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } + "optional": true }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "delimit-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", + "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=", "dev": true }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true }, - "path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "dev": true }, - "picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, - "requires": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - } - }, - "postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, - "requires": {} - }, - "postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", - "dev": true, - "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.4" - } + "iso-url": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", + "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", + "dev": true }, - "postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "json-text-sequence": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", + "integrity": "sha1-py8hfcSvxGKf/1/rME3BvVGi89I=", "dev": true, "requires": { - "icss-utils": "^5.0.0" + "delimit-stream": "0.1.0" } }, - "postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } + "nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, - "pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, + "peer": true, "requires": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, "pvtsutils": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", "dev": true, - "peer": true, "requires": { "tslib": "^2.6.1" } @@ -4444,24 +2386,8 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", - "dev": true, - "peer": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -4473,79 +2399,39 @@ "util-deprecate": "^1.0.1" } }, - "rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "requires": { - "resolve": "^1.20.0" - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true - }, - "renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dev": true, - "requires": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" + "rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "@types/estree": "1.0.9", + "fsevents": "~2.3.2" } }, "safe-buffer": { @@ -4554,73 +2440,18 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, - "schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true - }, - "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, "simple-cbor": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==", "dev": true }, - "slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "dev": true, + "optional": true }, "source-map-js": { "version": "1.2.1", @@ -4629,10 +2460,10 @@ "dev": true }, "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "version": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "optional": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -4647,126 +2478,32 @@ "safe-buffer": "~5.2.0" } }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "style-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", - "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", - "dev": true, - "requires": {} - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "tapable": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "dev": true - }, - "terser": { - "version": "5.34.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", - "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - }, - "terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.20", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-loader": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", - "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4", - "source-map": "^0.7.4" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "peer": true } } }, - "tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "requires": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "tsconfig-paths-webpack-plugin": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", - "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^4.1.2" - } - }, "tslib": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", @@ -4777,32 +2514,8 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true - }, - "unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "dev": true, - "requires": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "requires": { - "punycode": "^2.1.0" - } + "peer": true }, "util-deprecate": { "version": "1.0.2", @@ -4810,28 +2523,49 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true - }, - "watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "fsevents": "~2.3.3", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "peer": true + } } }, + "vite-plugin-css-injected-by-js": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.2.tgz", + "integrity": "sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ==", + "dev": true, + "requires": {} + }, "webcrypto-core": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", "dev": true, - "peer": true, "requires": { "@peculiar/asn1-schema": "^2.3.13", "@peculiar/json-schema": "^1.1.12", @@ -4839,99 +2573,6 @@ "pvtsutils": "^1.3.5", "tslib": "^2.7.0" } - }, - "webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", - "dev": true, - "requires": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "dependencies": { - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true - } - } - }, - "webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "dependencies": { - "commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true } } } diff --git a/tools/ui/package.json b/tools/ui/package.json index d496abe41..aac2c7437 100644 --- a/tools/ui/package.json +++ b/tools/ui/package.json @@ -3,8 +3,10 @@ "version": "0.1.0", "description": "", "keywords": [], + "type": "module", "scripts": { - "build": "webpack" + "build": "vite build", + "dev": "vite" }, "devDependencies": { "@dfinity/agent": "2.1.3", @@ -12,16 +14,8 @@ "@dfinity/auth-client": "2.1.3", "@dfinity/identity": "2.1.3", "@dfinity/principal": "2.1.3", - "buffer": "6.0.3", - "copy-webpack-plugin": "^12.0.2", - "css-loader": "^7.1.2", - "html-webpack-plugin": "^5.6.0", - "style-loader": "^4.0.0", - "terser-webpack-plugin": "^5.3.10", - "ts-loader": "^9.5.1", - "tsconfig-paths-webpack-plugin": "^4.1.0", "typescript": "^5.6.2", - "webpack": "5.95.0", - "webpack-cli": "5.1.4" + "vite": "^6.0.0", + "vite-plugin-css-injected-by-js": "^3.5.2" } } diff --git a/tools/ui/src/favicon.ico b/tools/ui/public/favicon.ico similarity index 100% rename from tools/ui/src/favicon.ico rename to tools/ui/public/favicon.ico diff --git a/tools/ui/vite.config.ts b/tools/ui/vite.config.ts new file mode 100644 index 000000000..7d869f314 --- /dev/null +++ b/tools/ui/vite.config.ts @@ -0,0 +1,34 @@ +import { defineConfig } from "vite"; +import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"; + +// The `didjs` canister embeds the built frontend directly via +// `include_bytes!("../../dist/didjs/{index.html,index.js,favicon.ico}")` +// (see src/didjs/lib.rs). So the build must emit exactly those three files, +// with stable (un-hashed) names, into `dist/didjs/`: +// - index.html (this entry, processed by Vite) +// - index.js (single entry chunk; CSS is inlined into it) +// - favicon.ico (copied verbatim from the `public/` directory) +export default defineConfig({ + // Inline imported CSS into the JS bundle (the canister only serves index.js, + // not a separate stylesheet) — mirrors the previous webpack `style-loader`. + plugins: [cssInjectedByJsPlugin()], + // Some @dfinity packages reference `global`; map it to `globalThis` for the browser. + define: { + global: "globalThis", + }, + build: { + outDir: "dist/didjs", + emptyOutDir: true, + sourcemap: true, + target: "es2020", + cssCodeSplit: false, + rollupOptions: { + output: { + entryFileNames: "index.js", + chunkFileNames: "index.js", + assetFileNames: "[name][extname]", + inlineDynamicImports: true, + }, + }, + }, +}); diff --git a/tools/ui/webpack.config.js b/tools/ui/webpack.config.js deleted file mode 100644 index 00b183ac7..000000000 --- a/tools/ui/webpack.config.js +++ /dev/null @@ -1,116 +0,0 @@ -const path = require("path"); -const TerserPlugin = require("terser-webpack-plugin"); -const dfxJson = require("./dfx.json"); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); -const webpack = require("webpack"); - -// List of all aliases for canisters. This creates the module alias for -// the `import ... from "@dfinity/ic/canisters/xyz"` where xyz is the name of a -// canister. -const aliases = Object.entries(dfxJson.canisters).reduce( - (acc, [name, _value]) => { - // Get the network name, or `local` by default. - const networkName = process.env["DFX_NETWORK"] || "local"; - const outputRoot = path.join( - __dirname, - ".dfx", - networkName, - "canisters", - name - ); - - return { - ...acc, - ["dfx-generated/" + name]: path.join(outputRoot), - }; - }, - {} -); - -/** - * Generate a webpack configuration for a canister. - */ -function generateWebpackConfigForCanister(name, info) { - //if (typeof info.frontend !== "object") { - // return; - //} - - return { - mode: "production", - entry: { - index: path.join(__dirname, "src/index.ts"), - }, - target: 'web', - devtool: "source-map", - optimization: { - minimize: true, - minimizer: [new TerserPlugin()], - }, - resolve: { - plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })], - extensions: [".js", ".ts", ".jsx", ".tsx"], - fallback: { - //"assert": require.resolve("assert/"), - "buffer": require.resolve("buffer/"), - //"events": require.resolve("events/"), - //"stream": require.resolve("stream-browserify/"), - //"util": require.resolve("util/"), - }, - alias: aliases, - }, - output: { - filename: "[name].js", - path: path.join(__dirname, "dist", name), - }, - - // Depending in the language or framework you are using for - // front-end development, add module loaders to the default - // webpack configuration. For example, if you are using React - // modules and CSS as described in the "Adding a stylesheet" - // tutorial, uncomment the following lines: - module: { - rules: [ - { test: /\.css$/, use: ['style-loader','css-loader'] }, - { test: /\.(jsx|ts|tsx)$/, - use: { - loader: "ts-loader", - options: { - // eslint-disable-next-line no-undef - configFile: path.join(__dirname, 'tsconfig.json'), - projectReferences: true, - } - } - } - ] - }, - plugins: [ - new HtmlWebpackPlugin({ - template: 'src/candid.html', - filename: 'index.html', - }), - new CopyWebpackPlugin({ - patterns: [ - { - from: 'src/favicon.ico', - to: 'favicon.ico', - }, - ]}), - new webpack.ProvidePlugin({ - Buffer: [require.resolve('buffer/'), 'Buffer'], - //process: require.resolve('process/browser'), - }), - ], - }; -} - -// If you have additional webpack configurations you want to build -// as part of this configuration, add them to the section below. -module.exports = [ - ...Object.entries(dfxJson.canisters) - .map(([name, info]) => { - return generateWebpackConfigForCanister(name, info); - }) - .filter((x) => !!x), -]; From ac2ef66759b4df9b30b294c0354f82b0c1dcde64 Mon Sep 17 00:00:00 2001 From: raymondk Date: Wed, 17 Jun 2026 11:05:13 -0400 Subject: [PATCH 38/43] chore(ui): Update TS libraries to @icp-sdk (#735) Update the candid ui cansiter to use the new TS libraries. --- tools/ui/package-lock.json | 983 +++++++++---------------------------- tools/ui/package.json | 8 +- tools/ui/src/auth/auth.ts | 38 +- tools/ui/src/candid.ts | 56 ++- tools/ui/src/index.ts | 4 +- tools/ui/tsconfig.json | 15 +- 6 files changed, 311 insertions(+), 793 deletions(-) diff --git a/tools/ui/package-lock.json b/tools/ui/package-lock.json index c7f3dd1ef..63930fe78 100644 --- a/tools/ui/package-lock.json +++ b/tools/ui/package-lock.json @@ -8,11 +8,9 @@ "name": "ui", "version": "0.1.0", "devDependencies": { - "@dfinity/agent": "2.1.3", - "@dfinity/auth-client": "2.1.3", - "@dfinity/candid": "2.1.3", - "@dfinity/identity": "2.1.3", - "@dfinity/principal": "2.1.3", + "@icp-sdk/auth": "^7.1.0", + "@icp-sdk/canisters": "^3.6.0", + "@icp-sdk/core": "^5.4.0", "typescript": "^5.6.2", "vite": "^6.0.0", "vite-plugin-css-injected-by-js": "^3.5.2" @@ -21,74 +19,22 @@ "../agent-js/packages/agent": { "extraneous": true }, - "node_modules/@dfinity/agent": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-2.1.3.tgz", - "integrity": "sha512-4XmqhFR3GQSUrmx7lMFx7DyHEhFkM6nz4O9FeYJ/WpkmPe8tulKaAfgWbWdTSCjbd8meCgKVHo+QYj+JHXagcw==", + "node_modules/@dfinity/cbor": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@dfinity/cbor/-/cbor-0.2.3.tgz", + "integrity": "sha512-5GX+9tAf29r94Pxv8nCzrv2/t+AkOfem/c/G61+FA2Wadq7THvc5cvr/AVLH0LpukVfNSWRRHvl1fkCdiB9MMQ==", "dev": true, - "peer": true, - "dependencies": { - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.3.1", - "base64-arraybuffer": "^0.2.0", - "borc": "^2.1.1", - "buffer": "^6.0.3", - "simple-cbor": "^0.4.1" - }, - "peerDependencies": { - "@dfinity/candid": "^2.1.3", - "@dfinity/principal": "^2.1.3" - } - }, - "node_modules/@dfinity/auth-client": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/auth-client/-/auth-client-2.1.3.tgz", - "integrity": "sha512-6lxv7w8FWSnu5FxMa75O1lddUQJWDDMgJCG1DeYM4+08dWy0TxV/CeEo2uInAdqwlZECgQYB3yQEZKUNkbA6OQ==", - "dev": true, - "dependencies": { - "idb": "^7.0.2" - }, - "peerDependencies": { - "@dfinity/agent": "^2.1.3", - "@dfinity/identity": "^2.1.3", - "@dfinity/principal": "^2.1.3" - } + "license": "Apache-2.0" }, - "node_modules/@dfinity/candid": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-2.1.3.tgz", - "integrity": "sha512-Asn7AfydLhhk7E5z9oW+5UL6ne11gxFlYTxHuhrIc7FdqYlM5Flcq1Wfg9EzRa6Btdol3w58Bcph7Brwh1bcIQ==", + "node_modules/@dfinity/utils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-4.2.1.tgz", + "integrity": "sha512-5Rd0fflQzBf3h/doVvllJC0X+mrZPwbe6s9qfJm5jyJz07SEkN+cGmqY0HHMPDlCOohZgjDoAG6uwv+6cOMA+w==", "dev": true, + "license": "Apache-2.0", "peer": true, "peerDependencies": { - "@dfinity/principal": "^2.1.3" - } - }, - "node_modules/@dfinity/identity": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-2.1.3.tgz", - "integrity": "sha512-qII0V91S1YeIz5/XRHomwrUhTME+C3oqdTnb99tBitXA2Gq6LU2JaCLbKbN7ehhSyW6EjO4tySJxANz6hYENcQ==", - "dev": true, - "peer": true, - "dependencies": { - "@noble/curves": "^1.2.0", - "@noble/hashes": "^1.3.1", - "borc": "^2.1.1" - }, - "peerDependencies": { - "@dfinity/agent": "^2.1.3", - "@dfinity/principal": "^2.1.3", - "@peculiar/webcrypto": "^1.4.0" - } - }, - "node_modules/@dfinity/principal": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-2.1.3.tgz", - "integrity": "sha512-HtiAfZcs+ToPYFepVJdFlorIfPA56KzC6J97ZuH2lGNMTAfJA+NEBzLe476B4wVCAwZ0TiGJ27J4ks9O79DFEg==", - "dev": true, - "peer": true, - "dependencies": { - "@noble/hashes": "^1.3.1" + "@icp-sdk/core": "^5" } }, "node_modules/@esbuild/aix-ppc64": { @@ -533,77 +479,71 @@ "node": ">=18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "node_modules/@icp-sdk/auth": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@icp-sdk/auth/-/auth-7.1.0.tgz", + "integrity": "sha512-WpJiSN6E90kQlpumA835O8GO4A397tbkbikeJayumpO8BqePuwcXRvuV/Lc8pStmNbFmrBacDrWbHCRhKOzg1g==", "dev": true, - "optional": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@icp-sdk/signer": "^5.4.0", + "idb": "^7.1.1" }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@icp-sdk/core": "^5" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@icp-sdk/canisters": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@icp-sdk/canisters/-/canisters-3.6.0.tgz", + "integrity": "sha512-GALqQiywObRabz98etHccTq1DpLy5Sbx3adrYO27ffbDgZGLY5sZcAQfR0CkP0Da6KUEauzZk3gBi8UDzbydHg==", "dev": true, - "optional": true, - "engines": { - "node": ">=6.0.0" + "license": "Apache-2.0", + "dependencies": { + "@noble/hashes": "^1.8.0", + "base58-js": "^3.0.3", + "bech32": "^2.0.0", + "mime": "^3.0.0" + }, + "peerDependencies": { + "@dfinity/utils": "^4.1", + "@icp-sdk/core": "^5" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "node_modules/@icp-sdk/core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@icp-sdk/core/-/core-5.4.0.tgz", + "integrity": "sha512-yedhCkHIhCxI65W8id99Mi8fUgSyqKSTbD57AphquNcFV2/3Rjykng0/6H08mHswKydDcv6Y0+e8aPdrznewCw==", "dev": true, - "optional": true, + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@dfinity/cbor": "^0.2.3", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "asn1js": "^3.0.7" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "node_modules/@icp-sdk/signer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@icp-sdk/signer/-/signer-5.4.0.tgz", + "integrity": "sha512-l7HKJ02ZszAWvBV0dSW7uNVNVwsVAlWp9zMirHk/wrtQpglMDSxh1kcXiha5R5YkIooy/d5Pt8QKHDPG4Orj6A==", "dev": true, - "optional": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "optional": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "license": "Apache-2.0", + "peerDependencies": { + "@icp-sdk/core": "^5" } }, "node_modules/@noble/curves": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", - "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", "dev": true, + "license": "MIT", "dependencies": { - "@noble/hashes": "1.5.0" + "@noble/hashes": "1.8.0" }, "engines": { "node": "^14.21.3 || >=16" @@ -613,10 +553,11 @@ } }, "node_modules/@noble/hashes": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", - "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, + "license": "MIT", "engines": { "node": "^14.21.3 || >=16" }, @@ -624,46 +565,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@peculiar/asn1-schema": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", - "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", - "dev": true, - "dependencies": { - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2" - } - }, - "node_modules/@peculiar/json-schema": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", - "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", - "dev": true, - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@peculiar/webcrypto": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", - "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", - "dev": true, - "peer": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/json-schema": "^1.1.12", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2", - "webcrypto-core": "^1.8.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.62.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", @@ -1014,149 +915,84 @@ "win32" ] }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, - "node_modules/asn1js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", - "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", "dev": true, + "license": "MIT", "dependencies": { - "pvtsutils": "^1.3.2", - "pvutils": "^1.1.3", - "tslib": "^2.4.0" + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" }, - "engines": { - "node": ">=12.0.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/base64-arraybuffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", - "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", "dev": true, - "engines": { - "node": ">= 0.6.0" + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bignumber.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", - "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, - "engines": { - "node": "*" - } + "license": "MIT" }, - "node_modules/borc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", - "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "bignumber.js": "^9.0.0", - "buffer": "^5.5.0", - "commander": "^2.15.0", - "ieee754": "^1.1.13", - "iso-url": "~0.4.7", - "json-text-sequence": "~0.1.0", - "readable-stream": "^3.6.0" + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/borc/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "node": ">=12.0.0" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "node_modules/base58-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/base58-js/-/base58-js-3.0.3.tgz", + "integrity": "sha512-3hf42BysHnUqmZO7mK6e5X/hs1AvyEJIhdVLbG/Mxn/fhFnhGxOO37mWbMHg1RT4TxqcPKXgqj9/bp1YG0GBXA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "license": "MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", "dev": true, - "optional": true - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/delimit-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", - "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=", - "dev": true + "license": "MIT" }, "node_modules/esbuild": { "version": "0.25.12", @@ -1221,48 +1057,17 @@ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "dev": true }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/iso-url": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", - "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, "engines": { - "node": ">=10" - } - }, - "node_modules/json-text-sequence": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", - "integrity": "sha1-py8hfcSvxGKf/1/rME3BvVGi89I=", - "dev": true, - "dependencies": { - "delimit-stream": "0.1.0" + "node": ">=10.0.0" } }, "node_modules/nanoid": { @@ -1311,7 +1116,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -1322,35 +1126,23 @@ } }, "node_modules/pvtsutils": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", - "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.1" + "tslib": "^2.8.1" } }, "node_modules/pvutils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", - "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=16.0.0" } }, "node_modules/rollup": { @@ -1398,42 +1190,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/simple-cbor": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", - "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1443,26 +1199,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -1513,17 +1249,17 @@ } }, "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" }, "node_modules/typescript": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1532,12 +1268,6 @@ "node": ">=14.17" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, "node_modules/vite": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", @@ -1654,76 +1384,23 @@ "funding": { "url": "https://github.com/sponsors/jonschlinkert" } - }, - "node_modules/webcrypto-core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", - "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", - "dev": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.3.13", - "@peculiar/json-schema": "^1.1.12", - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.7.0" - } } }, "dependencies": { - "@dfinity/agent": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-2.1.3.tgz", - "integrity": "sha512-4XmqhFR3GQSUrmx7lMFx7DyHEhFkM6nz4O9FeYJ/WpkmPe8tulKaAfgWbWdTSCjbd8meCgKVHo+QYj+JHXagcw==", - "dev": true, - "peer": true, - "requires": { - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.3.1", - "base64-arraybuffer": "^0.2.0", - "borc": "^2.1.1", - "buffer": "^6.0.3", - "simple-cbor": "^0.4.1" - } - }, - "@dfinity/auth-client": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/auth-client/-/auth-client-2.1.3.tgz", - "integrity": "sha512-6lxv7w8FWSnu5FxMa75O1lddUQJWDDMgJCG1DeYM4+08dWy0TxV/CeEo2uInAdqwlZECgQYB3yQEZKUNkbA6OQ==", - "dev": true, - "requires": { - "idb": "^7.0.2" - } + "@dfinity/cbor": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@dfinity/cbor/-/cbor-0.2.3.tgz", + "integrity": "sha512-5GX+9tAf29r94Pxv8nCzrv2/t+AkOfem/c/G61+FA2Wadq7THvc5cvr/AVLH0LpukVfNSWRRHvl1fkCdiB9MMQ==", + "dev": true }, - "@dfinity/candid": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-2.1.3.tgz", - "integrity": "sha512-Asn7AfydLhhk7E5z9oW+5UL6ne11gxFlYTxHuhrIc7FdqYlM5Flcq1Wfg9EzRa6Btdol3w58Bcph7Brwh1bcIQ==", + "@dfinity/utils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-4.2.1.tgz", + "integrity": "sha512-5Rd0fflQzBf3h/doVvllJC0X+mrZPwbe6s9qfJm5jyJz07SEkN+cGmqY0HHMPDlCOohZgjDoAG6uwv+6cOMA+w==", "dev": true, "peer": true, "requires": {} }, - "@dfinity/identity": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-2.1.3.tgz", - "integrity": "sha512-qII0V91S1YeIz5/XRHomwrUhTME+C3oqdTnb99tBitXA2Gq6LU2JaCLbKbN7ehhSyW6EjO4tySJxANz6hYENcQ==", - "dev": true, - "peer": true, - "requires": { - "@noble/curves": "^1.2.0", - "@noble/hashes": "^1.3.1", - "borc": "^2.1.1" - } - }, - "@dfinity/principal": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-2.1.3.tgz", - "integrity": "sha512-HtiAfZcs+ToPYFepVJdFlorIfPA56KzC6J97ZuH2lGNMTAfJA+NEBzLe476B4wVCAwZ0TiGJ27J4ks9O79DFEg==", - "dev": true, - "peer": true, - "requires": { - "@noble/hashes": "^1.3.1" - } - }, "@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -1906,109 +1583,65 @@ "dev": true, "optional": true }, - "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "@icp-sdk/auth": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@icp-sdk/auth/-/auth-7.1.0.tgz", + "integrity": "sha512-WpJiSN6E90kQlpumA835O8GO4A397tbkbikeJayumpO8BqePuwcXRvuV/Lc8pStmNbFmrBacDrWbHCRhKOzg1g==", "dev": true, - "optional": true, "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@icp-sdk/signer": "^5.4.0", + "idb": "^7.1.1" } }, - "@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "optional": true - }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "optional": true - }, - "@jridgewell/source-map": { - "version": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "@icp-sdk/canisters": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@icp-sdk/canisters/-/canisters-3.6.0.tgz", + "integrity": "sha512-GALqQiywObRabz98etHccTq1DpLy5Sbx3adrYO27ffbDgZGLY5sZcAQfR0CkP0Da6KUEauzZk3gBi8UDzbydHg==", "dev": true, - "optional": true, "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@noble/hashes": "^1.8.0", + "base58-js": "^3.0.3", + "bech32": "^2.0.0", + "mime": "^3.0.0" } }, - "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "optional": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "@icp-sdk/core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@icp-sdk/core/-/core-5.4.0.tgz", + "integrity": "sha512-yedhCkHIhCxI65W8id99Mi8fUgSyqKSTbD57AphquNcFV2/3Rjykng0/6H08mHswKydDcv6Y0+e8aPdrznewCw==", "dev": true, - "optional": true, + "peer": true, "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@dfinity/cbor": "^0.2.3", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "asn1js": "^3.0.7" } }, + "@icp-sdk/signer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@icp-sdk/signer/-/signer-5.4.0.tgz", + "integrity": "sha512-l7HKJ02ZszAWvBV0dSW7uNVNVwsVAlWp9zMirHk/wrtQpglMDSxh1kcXiha5R5YkIooy/d5Pt8QKHDPG4Orj6A==", + "dev": true, + "requires": {} + }, "@noble/curves": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", - "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", "dev": true, "requires": { - "@noble/hashes": "1.5.0" + "@noble/hashes": "1.8.0" } }, "@noble/hashes": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", - "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true }, - "@peculiar/asn1-schema": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.13.tgz", - "integrity": "sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==", - "dev": true, - "requires": { - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2" - } - }, - "@peculiar/json-schema": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", - "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", - "dev": true, - "requires": { - "tslib": "^2.0.0" - } - }, - "@peculiar/webcrypto": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", - "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", - "dev": true, - "peer": true, - "requires": { - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/json-schema": "^1.1.12", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2", - "webcrypto-core": "^1.8.0" - } - }, "@rollup/rollup-android-arm-eabi": { "version": "4.62.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", @@ -2184,95 +1817,60 @@ "dev": true, "optional": true }, - "@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", "dev": true }, - "asn1js": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", - "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", "dev": true, "requires": { - "pvtsutils": "^1.3.2", - "pvutils": "^1.1.3", - "tslib": "^2.4.0" + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" } }, - "base64-arraybuffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", - "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bignumber.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", - "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", - "dev": true - }, - "borc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", - "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", + "@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", "dev": true, "requires": { - "bignumber.js": "^9.0.0", - "buffer": "^5.5.0", - "commander": "^2.15.0", - "ieee754": "^1.1.13", - "iso-url": "~0.4.7", - "json-text-sequence": "~0.1.0", - "readable-stream": "^3.6.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" } }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, + "asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", "dev": true, "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" } }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "optional": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "base58-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/base58-js/-/base58-js-3.0.3.tgz", + "integrity": "sha512-3hf42BysHnUqmZO7mK6e5X/hs1AvyEJIhdVLbG/Mxn/fhFnhGxOO37mWbMHg1RT4TxqcPKXgqj9/bp1YG0GBXA==", "dev": true }, - "delimit-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", - "integrity": "sha1-m4MZR3wOX4rrPONXrjBfwl6hzSs=", + "bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", "dev": true }, "esbuild": { @@ -2322,33 +1920,12 @@ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "dev": true }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "iso-url": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", - "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true }, - "json-text-sequence": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", - "integrity": "sha1-py8hfcSvxGKf/1/rME3BvVGi89I=", - "dev": true, - "requires": { - "delimit-stream": "0.1.0" - } - }, "nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", @@ -2366,7 +1943,6 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, - "peer": true, "requires": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -2374,31 +1950,20 @@ } }, "pvtsutils": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", - "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", "dev": true, "requires": { - "tslib": "^2.6.1" + "tslib": "^2.8.1" } }, "pvutils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", - "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", "dev": true }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "rollup": { "version": "4.62.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", @@ -2434,50 +1999,12 @@ "fsevents": "~2.3.2" } }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "simple-cbor": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", - "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, - "source-map-support": { - "version": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -2505,22 +2032,15 @@ } }, "tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, "typescript": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true, - "peer": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "vite": { @@ -2560,19 +2080,6 @@ "integrity": "sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ==", "dev": true, "requires": {} - }, - "webcrypto-core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", - "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", - "dev": true, - "requires": { - "@peculiar/asn1-schema": "^2.3.13", - "@peculiar/json-schema": "^1.1.12", - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.7.0" - } } } } diff --git a/tools/ui/package.json b/tools/ui/package.json index aac2c7437..fd5de9cb8 100644 --- a/tools/ui/package.json +++ b/tools/ui/package.json @@ -9,11 +9,9 @@ "dev": "vite" }, "devDependencies": { - "@dfinity/agent": "2.1.3", - "@dfinity/candid": "2.1.3", - "@dfinity/auth-client": "2.1.3", - "@dfinity/identity": "2.1.3", - "@dfinity/principal": "2.1.3", + "@icp-sdk/auth": "^7.1.0", + "@icp-sdk/canisters": "^3.6.0", + "@icp-sdk/core": "^5.4.0", "typescript": "^5.6.2", "vite": "^6.0.0", "vite-plugin-css-injected-by-js": "^3.5.2" diff --git a/tools/ui/src/auth/auth.ts b/tools/ui/src/auth/auth.ts index be2958460..96ea337c8 100644 --- a/tools/ui/src/auth/auth.ts +++ b/tools/ui/src/auth/auth.ts @@ -1,10 +1,10 @@ -import { Principal } from "@dfinity/principal" -import { authClient } from "../candid" +import { Principal } from "@icp-sdk/core/principal" +import { authClient, createAuthClient } from "../candid" import { refresh_actor } from "../index" import { dfinityLogo, copyIcon } from "./icons" export async function renderAuth() { - const is_logged = await authClient?.isAuthenticated(); + const is_logged = authClient?.isAuthenticated() ?? false; is_logged ? await insertLogout() : insertLoginForm(); } @@ -66,18 +66,17 @@ async function insertLoginForm() { } buttonLogin.addEventListener("click", async () => { - let config: any = { - identityProvider: provider, - onSuccess: async () => { - refresh_actor(cid); - insertLogout(); - }, - onError: (err: any) => console.error(err), - }; - if (origin) { - config = {...config, derivationOrigin: origin}; + try { + const client = createAuthClient({ + identityProvider: provider!, + ...(origin ? { derivationOrigin: origin } : {}), + }); + await client.signIn(); + refresh_actor(cid); + await insertLogout(); + } catch (err) { + console.error(err); } - await authClient?.login(config); }); } catch (err) { console.error(err); @@ -87,11 +86,11 @@ async function insertLoginForm() { } } -function insertLogout() { +async function insertLogout() { const auth = document.getElementById("authentication"); auth!.innerHTML = ""; - CopyId(); + await CopyId(); LogoutButton(); } @@ -110,7 +109,7 @@ function LogoutButton() { auth!.appendChild(buttonLogout); } -function CopyId() { +async function CopyId() { if (!authClient) { return; } @@ -119,7 +118,8 @@ function CopyId() { const copyText = document.createElement("span"); - const id = authClient.getIdentity().getPrincipal().toString(); + const identity = await authClient.getIdentity(); + const id = identity.getPrincipal().toString(); const idShort = id.slice(0, 5) + "..." + id.slice(-5); copyText.innerText = idShort; @@ -143,6 +143,6 @@ function CopyId() { } async function logout() { - await authClient?.logout(); + await authClient?.signOut(); window.location.reload(); } diff --git a/tools/ui/src/candid.ts b/tools/ui/src/candid.ts index 8cfaa473b..45657238f 100644 --- a/tools/ui/src/candid.ts +++ b/tools/ui/src/candid.ts @@ -1,11 +1,12 @@ import { EXTERNAL_CONFIG_PROMISE } from './external'; -import { Actor, HttpAgent, ActorSubclass, CanisterStatus, getManagementCanister } from '@dfinity/agent'; +import { Actor, HttpAgent, ActorSubclass, CanisterStatus } from '@icp-sdk/core/agent'; import { IDL, InputBox, renderInput, renderValue -} from '@dfinity/candid'; -import {Principal} from '@dfinity/principal' +} from '@icp-sdk/core/candid'; +import {Principal} from '@icp-sdk/core/principal' +import { IcManagementCanister } from '@icp-sdk/canisters/ic-management'; import './candid.css'; -import { AuthClient } from "@dfinity/auth-client"; +import { AuthClient, type AuthClientCreateOptions } from "@icp-sdk/auth/client"; declare var flamegraph: any; declare var d3: any; @@ -20,11 +21,27 @@ function isKnownMainnet(agent: HttpAgent) { export let authClient: AuthClient | undefined; +// Construct (or reconstruct) the shared AuthClient. The @icp-sdk/auth client +// takes the identity provider and derivation origin at construction time +// (unlike the old callback-based login()), so callers that know the provider +// — e.g. the login button derives it from the `ii` query param — build it here. +export function createAuthClient(options?: AuthClientCreateOptions): AuthClient { + authClient = new AuthClient({ + idleOptions: { + disableIdle: true, + disableDefaultIdleCallback: true, + }, + ...options, + }); + return authClient; +} + const agent = HttpAgent.createSync(); if (!isKnownMainnet(agent)) { agent.fetchRootKey(); } +// TODO this should come back from a cookie like with the asset canister function getCanisterId(): Principal { // Check the query params. const maybeCanisterId = new URLSearchParams(window.location.search).get( @@ -81,16 +98,12 @@ export async function fetchActor(canisterId: Principal): Promise const dataUri = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(js); const candid: any = await eval('import("' + dataUri + '")'); - authClient = authClient ?? (await AuthClient.create({ - idleOptions: { - disableIdle: true, - disableDefaultIdleCallback: true, - }, - })) - if (await authClient.isAuthenticated()) { - agent.replaceIdentity(authClient.getIdentity()); + const client = authClient ?? createAuthClient(); + if (client.isAuthenticated()) { + const identity = await client.getIdentity(); + agent.replaceIdentity(identity); console.log("Authenticated with Internet Identity Principal") - console.log(authClient.getIdentity().getPrincipal().toString()) + console.log(identity.getPrincipal().toString()) } return Actor.createActor(candid.idlFactory, { agent, canisterId }); @@ -129,8 +142,10 @@ function timestampToString(nanoseconds: bigint) { let last_log_idx: bigint = -1n; export async function getCanisterLogs(canisterId: Principal, logger: any) { try { - const actor = getManagementCanister({ agent }); - const logs = await actor.fetch_canister_logs({ canister_id: canisterId }); + // fetch_canister_logs is a management-canister query; IcManagementCanister + // routes it to the target canister's subnet via the effective canister id. + const management = IcManagementCanister.create({ agent }); + const logs = await management.fetchCanisterLogs(canisterId); let array = logs.canister_log_records; const idx = array.findIndex((e) => e.idx > last_log_idx); if (idx > 0) { @@ -170,14 +185,11 @@ export async function getCycles(canisterId: Principal): Promise; decoded.forEach(([id, name]) => names[id] = name); } catch(err) { diff --git a/tools/ui/src/index.ts b/tools/ui/src/index.ts index 083ee4e9d..78f3a18f9 100644 --- a/tools/ui/src/index.ts +++ b/tools/ui/src/index.ts @@ -1,7 +1,7 @@ import { fetchActor, render, getCycles, getNames } from "./candid"; import { renderAuth } from "./auth/auth"; -import { Principal } from "@dfinity/principal"; -import { ActorSubclass } from "@dfinity/agent"; +import { Principal } from "@icp-sdk/core/principal"; +import { ActorSubclass } from "@icp-sdk/core/agent"; let actor: ActorSubclass | undefined; diff --git a/tools/ui/tsconfig.json b/tools/ui/tsconfig.json index 536c31cce..3a27e8d7e 100644 --- a/tools/ui/tsconfig.json +++ b/tools/ui/tsconfig.json @@ -2,17 +2,18 @@ "compilerOptions": { "allowJs": false, "baseUrl": "./", - "composite": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "incremental": true, - "lib": ["dom", "es2020"], - "module": "commonjs", - "outDir": "ts-out/", + "lib": ["dom", "dom.iterable", "es2020"], + "module": "ESNext", + "moduleResolution": "bundler", + "types": ["vite/client"], + "noEmit": true, + "skipLibCheck": true, "sourceMap": true, "strict": true, "target": "ES2020" }, - "include": ["src/**/*.ts", "src/**/*.css"], - "exclude": ["**/*.test.ts"], + "include": ["src"], + "exclude": ["**/*.test.ts"] } From 0e23227a9f8a188fd0a91c20f1852a4c086c3c24 Mon Sep 17 00:00:00 2001 From: raymondk Date: Wed, 17 Jun 2026 11:12:03 -0400 Subject: [PATCH 39/43] feat(ui): use candid:service metadata to lookup did (#736) Update after: This is to be updated after https://github.com/dfinity/candid/pull/735 is merged There's a been a "temporary hack" since 2022 - I spot checked canisters and they seem to have the standard "candid:service" metadata, eg: ``` # bitcoin minter icp canister metadata -n ic mqygn-kiaaa-aaaar-qaadq-cai "candid:service" ``` --- tools/ui/src/candid.ts | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/tools/ui/src/candid.ts b/tools/ui/src/candid.ts index 45657238f..7533d101a 100644 --- a/tools/ui/src/candid.ts +++ b/tools/ui/src/candid.ts @@ -78,19 +78,9 @@ export async function fetchActor(canisterId: Principal): Promise } } if (!js) { + // Read the interface from the canister's `candid:service` metadata — the + // standard, certified way for a canister to expose its Candid interface. js = await getDidJsFromMetadata(canisterId); - if (!js) { - try { - js = await getDidJsFromTmpHack(canisterId); - } catch(err) { - if (/no query method/.test(err as any)) { - console.warn(err); - js = undefined; - } else { - throw(err); - } - } - } } if (!js) { throw new Error('Cannot fetch candid file'); @@ -201,14 +191,20 @@ async function getDidJsFromPostMessage(canisterId: Principal): Promise{}) } + async function getDidJsFromMetadata(canisterId: Principal): Promise { - const status = await CanisterStatus.request({ agent, canisterId, paths: ['candid'] }); - const did = status.get('candid') as string | null; - if (did) { - return didToJs(did); - } else { - return undefined; + // The 'candid' path resolves to the `candid:service` metadata section and is + // read (and certified) via the canister's read_state endpoint. + try { + const status = await CanisterStatus.request({ agent, canisterId, paths: ['candid'] }); + const did = status.get('candid') as string | null; + if (did) { + return didToJs(did); + } + } catch (err) { + console.warn('Failed to read candid:service metadata:', err); } + return undefined; } export async function getProfiling(canisterId: Principal): Promise|undefined> { @@ -293,15 +289,6 @@ async function renderFlameGraph(profiler: any) { } } -async function getDidJsFromTmpHack(canisterId: Principal): Promise { - const common_interface: IDL.InterfaceFactory = ({ IDL }) => IDL.Service({ - __get_candid_interface_tmp_hack: IDL.Func([], [IDL.Text], ['query']), - }); - const actor: ActorSubclass = Actor.createActor(common_interface, { agent, canisterId }); - const candid_source = await actor.__get_candid_interface_tmp_hack() as string; - return didToJs(candid_source); -} - async function didToJs(candid_source: string): Promise { // call didjs canister const didjs_id = getCanisterId(); From 88697bbdb9707aa12bc356f5bae73cb4faa4f1ac Mon Sep 17 00:00:00 2001 From: raymondk Date: Wed, 17 Jun 2026 11:23:48 -0400 Subject: [PATCH 40/43] feat(ui): Bump to recent cdk and add asset certification and env cookie (#737) Don't merge before https://github.com/dfinity/candid/pull/736 This bumps the cdk to a more recent version so we can use environment variables and adds asset certification. Next steps will be to: - use environment variable to figure out the canister id on the frontend. - Update the vite config so the frontend can be iterated on without the canister (if possible) --- tools/ui/Cargo.lock | 194 ++++++++++++++++++++---- tools/ui/src/didjs/Cargo.toml | 5 +- tools/ui/src/didjs/lib.rs | 278 +++++++++++++++++++++++++--------- 3 files changed, 378 insertions(+), 99 deletions(-) diff --git a/tools/ui/Cargo.lock b/tools/ui/Cargo.lock index 9a063c590..6797e098d 100644 --- a/tools/ui/Cargo.lock +++ b/tools/ui/Cargo.lock @@ -148,7 +148,7 @@ dependencies = [ "serde", "serde_bytes", "stacker", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -177,7 +177,7 @@ dependencies = [ "logos", "num-bigint", "pretty", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -249,6 +249,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.111", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.111", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -261,10 +296,11 @@ version = "0.1.0" dependencies = [ "candid", "candid_parser", + "hex", "ic-cdk", "ic-http-certification", + "percent-encoding", "serde", - "serde_bytes", ] [[package]] @@ -370,6 +406,12 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -389,35 +431,44 @@ dependencies = [ [[package]] name = "ic-cdk" -version = "0.16.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50afd39a4c2022ca6cbdf15dbd653b33eeaae9010748b74006bbd717aa7c51a" +checksum = "818d6d5416a8f0212e1b132703b0da51e36c55f2b96677e96f2bbe7702e1bd85" dependencies = [ "candid", "ic-cdk-executor", "ic-cdk-macros", + "ic-error-types", + "ic-management-canister-types", "ic0", + "pin-project-lite", "serde", "serde_bytes", + "slotmap", + "thiserror 2.0.18", ] [[package]] name = "ic-cdk-executor" -version = "0.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903057edd3d4ff4b3fe44a64eaee1ceb73f579ba29e3ded372b63d291d7c16c2" +checksum = "33716b730ded33690b8a704bff3533fda87d229e58046823647d28816e9bcee7" +dependencies = [ + "ic0", + "slotmap", + "smallvec", +] [[package]] name = "ic-cdk-macros" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4d857135deef20cc7ea8f3869a30cd9cfeb1392b3a81043790b2cd82adc3e0" +checksum = "66dad91a214945cb3605bc9ef6901b87e2ac41e3624284c2cabba49d43aa4f43" dependencies = [ "candid", + "darling", "proc-macro2", "quote", - "serde", - "serde_tokenstream", "syn 2.0.111", ] @@ -432,6 +483,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "ic-error-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbeeb3d91aa179d6496d7293becdacedfc413c825cac79fd54ea1906f003ee55" +dependencies = [ + "serde", + "strum", + "strum_macros", +] + [[package]] name = "ic-http-certification" version = "2.6.0" @@ -444,10 +506,21 @@ dependencies = [ "ic-representation-independent-hash", "serde", "serde_cbor", - "thiserror", + "thiserror 1.0.69", "urlencoding", ] +[[package]] +name = "ic-management-canister-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3149217e24186df3f13dc45eee14cdb3e5cad07d0b2b67bd53555c1c55462957" +dependencies = [ + "candid", + "serde", + "serde_bytes", +] + [[package]] name = "ic-representation-independent-hash" version = "2.6.0" @@ -459,9 +532,9 @@ dependencies = [ [[package]] name = "ic0" -version = "0.23.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" +checksum = "c77c8932bff1f09502d0d8c079d5a206a06afe523e35e816162cf4d30b5bf80d" [[package]] name = "ic_principal" @@ -472,9 +545,15 @@ dependencies = [ "data-encoding", "serde", "sha2", - "thiserror", + "thiserror 1.0.69", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "2.12.1" @@ -685,6 +764,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "petgraph" version = "0.6.5" @@ -710,6 +795,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -772,7 +863,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -881,18 +972,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "serde_tokenstream" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.111", -] - [[package]] name = "sha2" version = "0.10.9" @@ -916,6 +995,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -947,6 +1035,34 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.111", +] + [[package]] name = "syn" version = "1.0.109" @@ -995,7 +1111,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -1009,6 +1134,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "tiny-keccak" version = "2.0.2" diff --git a/tools/ui/src/didjs/Cargo.toml b/tools/ui/src/didjs/Cargo.toml index 47bc75b2a..19831b7ad 100644 --- a/tools/ui/src/didjs/Cargo.toml +++ b/tools/ui/src/didjs/Cargo.toml @@ -10,7 +10,8 @@ crate-type = ["cdylib"] [dependencies] candid = "0.10" candid_parser = "0.1" -ic-cdk = "0.16" +ic-cdk = "0.19" ic-http-certification = { git = "https://github.com/dfinity/response-verification", rev = "35dd2bd316682cbd3d33720af7e1dd14ffc47b50" } +hex = "0.4.3" +percent-encoding = "2.3" serde = "1.0" -serde_bytes = "0.11" diff --git a/tools/ui/src/didjs/lib.rs b/tools/ui/src/didjs/lib.rs index afbd2800d..63f4cf05f 100644 --- a/tools/ui/src/didjs/lib.rs +++ b/tools/ui/src/didjs/lib.rs @@ -1,23 +1,17 @@ use candid::types::{subtype, Type, TypeInner}; -use candid_parser::{check_prog, CandidType, Deserialize, IDLProg, TypeEnv}; -use ic_cdk::api::{data_certificate, set_certified_data}; +use candid_parser::{check_prog, IDLProg, TypeEnv}; +use ic_cdk::api::{ + certified_data_set, data_certificate, env_var_count, env_var_name, env_var_value, root_key, +}; use ic_cdk::{init, post_upgrade}; use ic_http_certification::{ - utils::{add_skip_certification_header, skip_certification_certified_data}, - HttpResponse, + utils::add_v2_certificate_header, DefaultCelBuilder, DefaultResponseCertification, + HttpCertification, HttpCertificationPath, HttpCertificationTree, HttpCertificationTreeEntry, + HttpRequest, HttpResponse, CERTIFICATE_EXPRESSION_HEADER_NAME, }; - -#[derive(CandidType, Deserialize)] -pub struct HeaderField(pub String, pub String); - -#[derive(CandidType, Deserialize)] -pub struct HttpRequest { - pub method: String, - pub url: String, - pub headers: Vec, - #[serde(with = "serde_bytes")] - pub body: Vec, -} +use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; +use std::cell::RefCell; +use std::collections::{BTreeMap, HashMap}; #[ic_cdk::query] fn did_to_js(prog: String) -> Option { @@ -53,6 +47,16 @@ fn binding(prog: String, lang: String) -> Option { Some(res) } +#[init] +fn init() { + certify_all_assets(); +} + +#[post_upgrade] +fn post_upgrade() { + init(); +} + #[ic_cdk::query] fn merge_init_args(prog: String, init_args: String) -> Option { let (env, actor) = candid_parser::utils::merge_init_args(&prog, &init_args).ok()?; @@ -82,66 +86,204 @@ fn subtype(new: String, old: String) -> Result<(), String> { } } -fn retrieve(path: &str) -> Option<(&str, &'static [u8])> { - match path { - "/index.html" | "/" => Some(("text/html", include_bytes!("../../dist/didjs/index.html"))), - "/favicon.ico" => Some(( - "image/x-icon", - include_bytes!("../../dist/didjs/favicon.ico"), - )), - "/index.js" => Some(( - "application/javascript", - include_bytes!("../../dist/didjs/index.js"), - )), - _ => None, - } -} - -fn get_path(url: &str) -> Option<&str> { - url.split('?').next() -} - #[ic_cdk::query] fn http_request(request: HttpRequest) -> HttpResponse<'static> { //TODO add /canister_id/ as endpoint when ICQC is available. - let path = get_path(request.url.as_str()).unwrap_or("/"); - let mut response = if let Some((content_type, bytes)) = retrieve(path) { - HttpResponse::builder() - .with_status_code(200) - .with_headers(vec![ - ("Content-Type".to_string(), content_type.to_string()), - ("Content-Length".to_string(), format!("{}", bytes.len())), - ("Cache-Control".to_string(), format!("max-age={}", 600)), - ( - "Cross-Origin-Embedder-Policy".to_string(), - "require-corp".to_string(), - ), - ( - "Cross-Origin-Resource-Policy".to_string(), - "cross-origin".to_string(), - ), - ]) - .with_body(bytes) - .build() - } else { - HttpResponse::builder() - .with_status_code(404) - .with_headers(Vec::new()) - .with_body(path.as_bytes().to_vec()) - .build() - }; - add_skip_certification_header(data_certificate().unwrap(), &mut response); + let req_path = request.get_path().unwrap_or_else(|_| "/".to_string()); + + // Look up the exact-path response, falling back to the certified 404 wildcard. + let (tree_path, certified) = RESPONSES.with_borrow(|responses| { + if let Some(certified) = responses.get(&req_path) { + (HttpCertificationPath::exact(&req_path), certified.clone()) + } else { + ( + not_found_tree_path(), + NOT_FOUND.with_borrow(|n| n.clone().unwrap()), + ) + } + }); + + let CertifiedHttpResponse { + mut response, + certification, + } = certified; + + HTTP_TREE.with_borrow(|tree| { + let witness = tree + .witness( + &HttpCertificationTreeEntry::new(&tree_path, certification), + &req_path, + ) + .unwrap(); + add_v2_certificate_header( + &data_certificate().expect("No data certificate available"), + &mut response, + &witness, + &tree_path.to_expr_path(), + ); + }); + response } -#[init] -fn init() { - set_certified_data(&skip_certification_certified_data()); + +// Storage + + +// The static assets served by this canister, baked into the wasm at build time. +const INDEX_HTML: &[u8] = include_bytes!("../../dist/didjs/index.html"); +const FAVICON: &[u8] = include_bytes!("../../dist/didjs/favicon.ico"); +const INDEX_JS: &[u8] = include_bytes!("../../dist/didjs/index.js"); + +// `/` and `/index.html` both serve the SPA entry point. +const ASSETS: &[(&str, &str, &[u8])] = &[ + ("/", "text/html", INDEX_HTML), + ("/index.html", "text/html", INDEX_HTML), + ("/favicon.ico", "image/x-icon", FAVICON), + ("/index.js", "application/javascript", INDEX_JS), +]; + +// Body returned (and certified) for any request that doesn't match a known asset. +const NOT_FOUND_BODY: &[u8] = b"Not found"; + +// A pre-certified response, ready to be served alongside its tree witness. +#[derive(Clone)] +struct CertifiedHttpResponse { + response: HttpResponse<'static>, + certification: HttpCertification, } -#[post_upgrade] -fn post_upgrade() { - init(); +thread_local! { + static HTTP_TREE: RefCell = RefCell::new(HttpCertificationTree::default()); + // Exact-path responses, keyed by request path. + static RESPONSES: RefCell> = RefCell::new(HashMap::new()); + // Wildcard fallback served for any unmatched path. + static NOT_FOUND: RefCell> = const { RefCell::new(None) }; +} + +// The `ic_env` cookie carries the canister environment (the IC root key and any +// `PUBLIC_*` environment variables) to the frontend. It is only attached to +// `text/html` responses. Mirrors the asset canister in dfinity/sdk: +// src/canisters/frontend/ic-certified-assets/src/{cookies,system_context/canister_env}.rs +const SET_COOKIE_HEADER_NAME: &str = "Set-Cookie"; +const IC_ENV_COOKIE_NAME: &str = "ic_env"; +const IC_ROOT_KEY_VALUE_KEY: &str = "ic_root_key"; +const PUBLIC_ENV_VAR_NAME_PREFIX: &str = "PUBLIC_"; +const COOKIE_VALUES_SEPARATOR: &str = "&"; + +// Builds the (URL-encoded) `ic_env` cookie value from the canister environment: +// `ic_root_key=` followed by each `PUBLIC_*` env var, joined by `&`. +fn encoded_canister_env() -> String { + let mut values = vec![format!( + "{IC_ROOT_KEY_VALUE_KEY}={}", + hex::encode(root_key()) + )]; + + // BTreeMap keeps the env vars in a stable, sorted order. + let mut public_env_vars = BTreeMap::new(); + for i in 0..env_var_count() { + let name = env_var_name(i); + if name.starts_with(PUBLIC_ENV_VAR_NAME_PREFIX) { + let value = env_var_value(&name); + public_env_vars.insert(name, value); + } + } + values.extend(public_env_vars.iter().map(|(k, v)| format!("{k}={v}"))); + + utf8_percent_encode(&values.join(COOKIE_VALUES_SEPARATOR), NON_ALPHANUMERIC).to_string() +} + +// Certification + +fn build_response( + status_code: u16, + content_type: &str, + body: &'static [u8], + cel_expr_str: &str, + encoded_canister_env: &str, +) -> HttpResponse<'static> { + let mut headers = vec![ + ( + CERTIFICATE_EXPRESSION_HEADER_NAME.to_string(), + cel_expr_str.to_string(), + ), + ("Content-Type".to_string(), content_type.to_string()), + ("Content-Length".to_string(), format!("{}", body.len())), + ("Cache-Control".to_string(), format!("max-age={}", 600)), + ( + "Cross-Origin-Embedder-Policy".to_string(), + "require-corp".to_string(), + ), + ( + "Cross-Origin-Resource-Policy".to_string(), + "cross-origin".to_string(), + ), + ]; + + // Attach the canister environment cookie to HTML responses only. + if content_type == "text/html" { + headers.push(( + SET_COOKIE_HEADER_NAME.to_string(), + format!("{IC_ENV_COOKIE_NAME}={encoded_canister_env}; SameSite=Lax"), + )); + } + + HttpResponse::builder() + .with_status_code(status_code) + .with_headers(headers) + .with_body(body) + .build() +} + +fn certify_all_assets() { + let canister_env = encoded_canister_env(); + + // We certify the full response (no excluded headers) and exclude the request. + let cel = DefaultCelBuilder::response_only_certification() + .with_response_certification(DefaultResponseCertification::response_header_exclusions( + vec![], + )) + .build(); + let cel_str = cel.to_string(); + + HTTP_TREE.with_borrow_mut(|tree| { + // Certify each static asset at its exact path. + for (path, content_type, body) in ASSETS { + let response = build_response(200, content_type, body, &cel_str, &canister_env); + let certification = HttpCertification::response_only(&cel, &response, None).unwrap(); + let tree_path = HttpCertificationPath::exact(*path); + tree.insert(&HttpCertificationTreeEntry::new(&tree_path, certification)); + RESPONSES.with_borrow_mut(|responses| { + responses.insert( + path.to_string(), + CertifiedHttpResponse { + response, + certification, + }, + ); + }); + } + + // Certify a 404 response under a wildcard path, covering every other request. + let response = build_response(404, "text/plain", NOT_FOUND_BODY, &cel_str, &canister_env); + let certification = HttpCertification::response_only(&cel, &response, None).unwrap(); + tree.insert(&HttpCertificationTreeEntry::new( + not_found_tree_path(), + certification, + )); + NOT_FOUND.with_borrow_mut(|not_found| { + *not_found = Some(CertifiedHttpResponse { + response, + certification, + }); + }); + + certified_data_set(tree.root_hash()); + }); +} + +fn not_found_tree_path() -> HttpCertificationPath<'static> { + HttpCertificationPath::wildcard("") } ic_cdk::export_candid!(); From e131cfddae78d8da9032c2b21a41a245eaf82259 Mon Sep 17 00:00:00 2001 From: raymondk Date: Wed, 17 Jun 2026 17:05:03 -0400 Subject: [PATCH 41/43] chore(ui): Use the environment variables to lookup the canister id (#738) --- tools/ui/Cargo.lock | 39 +- tools/ui/README.md | 17 +- tools/ui/package-lock.json | 771 +--------------------------------- tools/ui/src/candid.ts | 37 +- tools/ui/src/didjs/Cargo.toml | 2 +- tools/ui/src/didjs/lib.rs | 40 +- 6 files changed, 45 insertions(+), 861 deletions(-) diff --git a/tools/ui/Cargo.lock b/tools/ui/Cargo.lock index 6797e098d..dfc1789f2 100644 --- a/tools/ui/Cargo.lock +++ b/tools/ui/Cargo.lock @@ -251,9 +251,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.11" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", @@ -261,11 +261,10 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.11" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", @@ -275,9 +274,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.11" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", @@ -431,20 +430,17 @@ dependencies = [ [[package]] name = "ic-cdk" -version = "0.19.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818d6d5416a8f0212e1b132703b0da51e36c55f2b96677e96f2bbe7702e1bd85" +checksum = "6a7971f4983db147afbbc4e7f87f60b09fcd60ac707af37ff3d2468dcddbf551" dependencies = [ "candid", "ic-cdk-executor", "ic-cdk-macros", "ic-error-types", - "ic-management-canister-types", "ic0", "pin-project-lite", "serde", - "serde_bytes", - "slotmap", "thiserror 2.0.18", ] @@ -461,9 +457,9 @@ dependencies = [ [[package]] name = "ic-cdk-macros" -version = "0.19.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66dad91a214945cb3605bc9ef6901b87e2ac41e3624284c2cabba49d43aa4f43" +checksum = "a7c20c002200c720958f321bb78b4d4987fc2c380bf9ef69ed4404730810f45f" dependencies = [ "candid", "darling", @@ -510,17 +506,6 @@ dependencies = [ "urlencoding", ] -[[package]] -name = "ic-management-canister-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3149217e24186df3f13dc45eee14cdb3e5cad07d0b2b67bd53555c1c55462957" -dependencies = [ - "candid", - "serde", - "serde_bytes", -] - [[package]] name = "ic-representation-independent-hash" version = "2.6.0" @@ -820,9 +805,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] diff --git a/tools/ui/README.md b/tools/ui/README.md index 40c416170..6a6db54be 100644 --- a/tools/ui/README.md +++ b/tools/ui/README.md @@ -2,8 +2,16 @@ This canister generates a front-end UI for any canister running on the Internet Computer. -The backend `didjs` is build with the [Rust CDK](https://github.com/dfinity/cdk-rs) and the [Candid](../../rust/) crate to convert did file into JavaScript. -The frontend `ui` fetches the Candid interface from the running canister (currently only canisters built by Motoko expose the Candid interface) and renders the UI based on the interface signature. +Thi canister `didjs` is built with the [Rust CDK](https://github.com/dfinity/cdk-rs) and the [Candid](../../rust/) crate to convert did file into JavaScript. + +It bundles the certified frontend assets and serves them with a cookie compatible with [CanisterEnv](https://js.icp.build/core/v5.3/libs/agent/canister-env/api/interfaces/canisterenv). The CanisterEnv will contain a `CANISTER_ID` value that the frontend can use to call back to the canister. + +The frontend fetches the Candid interface from the target canister by looking it up in the metadata under `candid:service` and renders the UI based on the interface signature. + +## Q&A + +### Why a single canister? +This canister is also meant to be deployed on the local network during local development. It's easier if everything is self contained. ## Build @@ -30,6 +38,5 @@ local replica state under `.icp/cache/` is ephemeral and git-ignored. ## Usage -Open the `ui` canister in a browser and supply the id parameter in the URL to render a specific canister, -e.g., `localhost:8000/?canisterId=ui-canister-id&id=target-canister-id` for local dev, -or `ui-canister-id.ic0.app/?id=target-canister-id` for the main network. +Open the canister in a browser and supply the id parameter in the URL to render a specific canister, +e.g., `didjs.local.localhost:8000/?id=target-canister-id` for local dev diff --git a/tools/ui/package-lock.json b/tools/ui/package-lock.json index 63930fe78..a8dae33e1 100644 --- a/tools/ui/package-lock.json +++ b/tools/ui/package-lock.json @@ -1,7 +1,7 @@ { "name": "ui", "version": "0.1.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -16,13 +16,8 @@ "vite-plugin-css-injected-by-js": "^3.5.2" } }, - "../agent-js/packages/agent": { - "extraneous": true - }, "node_modules/@dfinity/cbor": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@dfinity/cbor/-/cbor-0.2.3.tgz", - "integrity": "sha512-5GX+9tAf29r94Pxv8nCzrv2/t+AkOfem/c/G61+FA2Wadq7THvc5cvr/AVLH0LpukVfNSWRRHvl1fkCdiB9MMQ==", "dev": true, "license": "Apache-2.0" }, @@ -107,8 +102,6 @@ }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -481,8 +474,6 @@ }, "node_modules/@icp-sdk/auth": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@icp-sdk/auth/-/auth-7.1.0.tgz", - "integrity": "sha512-WpJiSN6E90kQlpumA835O8GO4A397tbkbikeJayumpO8BqePuwcXRvuV/Lc8pStmNbFmrBacDrWbHCRhKOzg1g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -512,8 +503,6 @@ }, "node_modules/@icp-sdk/core": { "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@icp-sdk/core/-/core-5.4.0.tgz", - "integrity": "sha512-yedhCkHIhCxI65W8id99Mi8fUgSyqKSTbD57AphquNcFV2/3Rjykng0/6H08mHswKydDcv6Y0+e8aPdrznewCw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -528,8 +517,6 @@ }, "node_modules/@icp-sdk/signer": { "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@icp-sdk/signer/-/signer-5.4.0.tgz", - "integrity": "sha512-l7HKJ02ZszAWvBV0dSW7uNVNVwsVAlWp9zMirHk/wrtQpglMDSxh1kcXiha5R5YkIooy/d5Pt8QKHDPG4Orj6A==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -538,8 +525,6 @@ }, "node_modules/@noble/curves": { "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", "dev": true, "license": "MIT", "dependencies": { @@ -554,8 +539,6 @@ }, "node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { @@ -595,8 +578,6 @@ }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", - "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", "cpu": [ "arm64" ], @@ -917,8 +898,6 @@ }, "node_modules/@scure/base": { "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", "dev": true, "license": "MIT", "funding": { @@ -927,8 +906,6 @@ }, "node_modules/@scure/bip32": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", "dev": true, "license": "MIT", "dependencies": { @@ -942,8 +919,6 @@ }, "node_modules/@scure/bip39": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", "dev": true, "license": "MIT", "dependencies": { @@ -956,15 +931,11 @@ }, "node_modules/@types/estree": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/asn1js": { "version": "3.0.10", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", - "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -996,8 +967,6 @@ }, "node_modules/esbuild": { "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1038,10 +1007,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -1053,9 +1019,8 @@ }, "node_modules/idb": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/mime": { "version": "3.0.0", @@ -1072,8 +1037,6 @@ }, "node_modules/nanoid": { "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -1091,15 +1054,11 @@ }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/postcss": { "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -1127,8 +1086,6 @@ }, "node_modules/pvtsutils": { "version": "1.3.6", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", - "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", "dev": true, "license": "MIT", "dependencies": { @@ -1137,8 +1094,6 @@ }, "node_modules/pvutils": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", - "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", "dev": true, "license": "MIT", "engines": { @@ -1147,8 +1102,6 @@ }, "node_modules/rollup": { "version": "4.62.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", - "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", "dev": true, "license": "MIT", "dependencies": { @@ -1192,17 +1145,14 @@ }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/tinyglobby": { "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -1218,8 +1168,6 @@ }, "node_modules/tinyglobby/node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -1236,8 +1184,6 @@ }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "peer": true, @@ -1250,16 +1196,13 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD" }, "node_modules/typescript": { "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1270,8 +1213,6 @@ }, "node_modules/vite": { "version": "6.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", - "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", "dependencies": { @@ -1345,8 +1286,6 @@ }, "node_modules/vite-plugin-css-injected-by-js": { "version": "3.5.2", - "resolved": "https://registry.npmjs.org/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.2.tgz", - "integrity": "sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1355,8 +1294,6 @@ }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -1373,8 +1310,6 @@ }, "node_modules/vite/node_modules/picomatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "peer": true, @@ -1385,701 +1320,5 @@ "url": "https://github.com/sponsors/jonschlinkert" } } - }, - "dependencies": { - "@dfinity/cbor": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@dfinity/cbor/-/cbor-0.2.3.tgz", - "integrity": "sha512-5GX+9tAf29r94Pxv8nCzrv2/t+AkOfem/c/G61+FA2Wadq7THvc5cvr/AVLH0LpukVfNSWRRHvl1fkCdiB9MMQ==", - "dev": true - }, - "@dfinity/utils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@dfinity/utils/-/utils-4.2.1.tgz", - "integrity": "sha512-5Rd0fflQzBf3h/doVvllJC0X+mrZPwbe6s9qfJm5jyJz07SEkN+cGmqY0HHMPDlCOohZgjDoAG6uwv+6cOMA+w==", - "dev": true, - "peer": true, - "requires": {} - }, - "@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "dev": true, - "optional": true - }, - "@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "dev": true, - "optional": true - }, - "@icp-sdk/auth": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@icp-sdk/auth/-/auth-7.1.0.tgz", - "integrity": "sha512-WpJiSN6E90kQlpumA835O8GO4A397tbkbikeJayumpO8BqePuwcXRvuV/Lc8pStmNbFmrBacDrWbHCRhKOzg1g==", - "dev": true, - "requires": { - "@icp-sdk/signer": "^5.4.0", - "idb": "^7.1.1" - } - }, - "@icp-sdk/canisters": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@icp-sdk/canisters/-/canisters-3.6.0.tgz", - "integrity": "sha512-GALqQiywObRabz98etHccTq1DpLy5Sbx3adrYO27ffbDgZGLY5sZcAQfR0CkP0Da6KUEauzZk3gBi8UDzbydHg==", - "dev": true, - "requires": { - "@noble/hashes": "^1.8.0", - "base58-js": "^3.0.3", - "bech32": "^2.0.0", - "mime": "^3.0.0" - } - }, - "@icp-sdk/core": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@icp-sdk/core/-/core-5.4.0.tgz", - "integrity": "sha512-yedhCkHIhCxI65W8id99Mi8fUgSyqKSTbD57AphquNcFV2/3Rjykng0/6H08mHswKydDcv6Y0+e8aPdrznewCw==", - "dev": true, - "peer": true, - "requires": { - "@dfinity/cbor": "^0.2.3", - "@noble/curves": "^1.9.7", - "@noble/hashes": "^1.8.0", - "@scure/bip32": "^1.7.0", - "@scure/bip39": "^1.6.0", - "asn1js": "^3.0.7" - } - }, - "@icp-sdk/signer": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@icp-sdk/signer/-/signer-5.4.0.tgz", - "integrity": "sha512-l7HKJ02ZszAWvBV0dSW7uNVNVwsVAlWp9zMirHk/wrtQpglMDSxh1kcXiha5R5YkIooy/d5Pt8QKHDPG4Orj6A==", - "dev": true, - "requires": {} - }, - "@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "dev": true, - "requires": { - "@noble/hashes": "1.8.0" - } - }, - "@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "dev": true - }, - "@rollup/rollup-android-arm-eabi": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", - "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-android-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", - "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", - "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", - "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", - "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", - "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", - "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", - "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", - "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", - "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", - "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-loong64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", - "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", - "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", - "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", - "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", - "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", - "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", - "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-musl": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", - "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-openbsd-x64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", - "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-openharmony-arm64": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", - "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", - "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", - "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-x64-gnu": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", - "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-x64-msvc": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", - "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", - "dev": true, - "optional": true - }, - "@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", - "dev": true - }, - "@scure/bip32": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", - "dev": true, - "requires": { - "@noble/curves": "~1.9.0", - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - } - }, - "@scure/bip39": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", - "dev": true, - "requires": { - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - } - }, - "@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true - }, - "asn1js": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", - "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", - "dev": true, - "requires": { - "pvtsutils": "^1.3.6", - "pvutils": "^1.1.5", - "tslib": "^2.8.1" - } - }, - "base58-js": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/base58-js/-/base58-js-3.0.3.tgz", - "integrity": "sha512-3hf42BysHnUqmZO7mK6e5X/hs1AvyEJIhdVLbG/Mxn/fhFnhGxOO37mWbMHg1RT4TxqcPKXgqj9/bp1YG0GBXA==", - "dev": true - }, - "bech32": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", - "dev": true - }, - "esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true - }, - "mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true - }, - "nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true - }, - "picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true - }, - "postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, - "requires": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - } - }, - "pvtsutils": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", - "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", - "dev": true, - "requires": { - "tslib": "^2.8.1" - } - }, - "pvutils": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", - "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", - "dev": true - }, - "rollup": { - "version": "4.62.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", - "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.62.0", - "@rollup/rollup-android-arm64": "4.62.0", - "@rollup/rollup-darwin-arm64": "4.62.0", - "@rollup/rollup-darwin-x64": "4.62.0", - "@rollup/rollup-freebsd-arm64": "4.62.0", - "@rollup/rollup-freebsd-x64": "4.62.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", - "@rollup/rollup-linux-arm-musleabihf": "4.62.0", - "@rollup/rollup-linux-arm64-gnu": "4.62.0", - "@rollup/rollup-linux-arm64-musl": "4.62.0", - "@rollup/rollup-linux-loong64-gnu": "4.62.0", - "@rollup/rollup-linux-loong64-musl": "4.62.0", - "@rollup/rollup-linux-ppc64-gnu": "4.62.0", - "@rollup/rollup-linux-ppc64-musl": "4.62.0", - "@rollup/rollup-linux-riscv64-gnu": "4.62.0", - "@rollup/rollup-linux-riscv64-musl": "4.62.0", - "@rollup/rollup-linux-s390x-gnu": "4.62.0", - "@rollup/rollup-linux-x64-gnu": "4.62.0", - "@rollup/rollup-linux-x64-musl": "4.62.0", - "@rollup/rollup-openbsd-x64": "4.62.0", - "@rollup/rollup-openharmony-arm64": "4.62.0", - "@rollup/rollup-win32-arm64-msvc": "4.62.0", - "@rollup/rollup-win32-ia32-msvc": "4.62.0", - "@rollup/rollup-win32-x64-gnu": "4.62.0", - "@rollup/rollup-win32-x64-msvc": "4.62.0", - "@types/estree": "1.0.9", - "fsevents": "~2.3.2" - } - }, - "source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true - }, - "tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "requires": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "dependencies": { - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - }, - "picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "peer": true - } - } - }, - "tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true - }, - "typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true - }, - "vite": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", - "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", - "dev": true, - "requires": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "fsevents": "~2.3.3", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "dependencies": { - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - }, - "picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "peer": true - } - } - }, - "vite-plugin-css-injected-by-js": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.2.tgz", - "integrity": "sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ==", - "dev": true, - "requires": {} - } } } diff --git a/tools/ui/src/candid.ts b/tools/ui/src/candid.ts index 7533d101a..f85bc262b 100644 --- a/tools/ui/src/candid.ts +++ b/tools/ui/src/candid.ts @@ -5,6 +5,7 @@ import { } from '@icp-sdk/core/candid'; import {Principal} from '@icp-sdk/core/principal' import { IcManagementCanister } from '@icp-sdk/canisters/ic-management'; +import { getCanisterEnv } from '@icp-sdk/core/agent/canister-env'; import './candid.css'; import { AuthClient, type AuthClientCreateOptions } from "@icp-sdk/auth/client"; @@ -13,11 +14,6 @@ declare var d3: any; const names: Record = {}; -function isKnownMainnet(agent: HttpAgent) { - // @ts-ignore - const hostname = agent.host.hostname; - return hostname.endsWith('.icp0.io') || hostname.endsWith('.ic0.app'); -} export let authClient: AuthClient | undefined; @@ -36,34 +32,9 @@ export function createAuthClient(options?: AuthClientCreateOptions): AuthClient return authClient; } +const canisterEnv = getCanisterEnv<{readonly "CANISTER_ID": string}>(); const agent = HttpAgent.createSync(); -if (!isKnownMainnet(agent)) { - agent.fetchRootKey(); -} - -// TODO this should come back from a cookie like with the asset canister -function getCanisterId(): Principal { - // Check the query params. - const maybeCanisterId = new URLSearchParams(window.location.search).get( - "canisterId" - ); - if (maybeCanisterId) { - return Principal.fromText(maybeCanisterId); - } - - // Return the first canister ID when resolving from the right hand side. - const domain = window.location.hostname.split(".").reverse(); - for (const subdomain of domain) { - try { - if (subdomain.length >= 25) { - // The following throws if it can't decode or the checksum is invalid. - return Principal.fromText(subdomain); - } - } catch (_) {} - } - - throw new Error("Could not find the canister ID."); -} +agent.rootKey = canisterEnv.IC_ROOT_KEY; export async function fetchActor(canisterId: Principal): Promise { let js; @@ -291,7 +262,7 @@ async function renderFlameGraph(profiler: any) { async function didToJs(candid_source: string): Promise { // call didjs canister - const didjs_id = getCanisterId(); + const didjs_id = canisterEnv["CANISTER_ID"]; const didjs_interface: IDL.InterfaceFactory = ({ IDL }) => IDL.Service({ did_to_js: IDL.Func([IDL.Text], [IDL.Opt(IDL.Text)], ['query']), }); diff --git a/tools/ui/src/didjs/Cargo.toml b/tools/ui/src/didjs/Cargo.toml index 19831b7ad..54e194b61 100644 --- a/tools/ui/src/didjs/Cargo.toml +++ b/tools/ui/src/didjs/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["cdylib"] [dependencies] candid = "0.10" candid_parser = "0.1" -ic-cdk = "0.19" +ic-cdk = "0.20" ic-http-certification = { git = "https://github.com/dfinity/response-verification", rev = "35dd2bd316682cbd3d33720af7e1dd14ffc47b50" } hex = "0.4.3" percent-encoding = "2.3" diff --git a/tools/ui/src/didjs/lib.rs b/tools/ui/src/didjs/lib.rs index 63f4cf05f..60fca3e31 100644 --- a/tools/ui/src/didjs/lib.rs +++ b/tools/ui/src/didjs/lib.rs @@ -1,7 +1,7 @@ use candid::types::{subtype, Type, TypeInner}; use candid_parser::{check_prog, IDLProg, TypeEnv}; use ic_cdk::api::{ - certified_data_set, data_certificate, env_var_count, env_var_name, env_var_value, root_key, + certified_data_set, data_certificate, root_key, }; use ic_cdk::{init, post_upgrade}; use ic_http_certification::{ @@ -11,7 +11,7 @@ use ic_http_certification::{ }; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use std::cell::RefCell; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; #[ic_cdk::query] fn did_to_js(prog: String) -> Option { @@ -126,10 +126,8 @@ fn http_request(request: HttpRequest) -> HttpResponse<'static> { response } - // Storage - // The static assets served by this canister, baked into the wasm at build time. const INDEX_HTML: &[u8] = include_bytes!("../../dist/didjs/index.html"); const FAVICON: &[u8] = include_bytes!("../../dist/didjs/favicon.ico"); @@ -161,36 +159,20 @@ thread_local! { static NOT_FOUND: RefCell> = const { RefCell::new(None) }; } -// The `ic_env` cookie carries the canister environment (the IC root key and any -// `PUBLIC_*` environment variables) to the frontend. It is only attached to -// `text/html` responses. Mirrors the asset canister in dfinity/sdk: -// src/canisters/frontend/ic-certified-assets/src/{cookies,system_context/canister_env}.rs +// The `ic_env` cookie carries environment data compatible with the canister environment +// variables cookie. It is only attached to `text/html` responses. const SET_COOKIE_HEADER_NAME: &str = "Set-Cookie"; const IC_ENV_COOKIE_NAME: &str = "ic_env"; -const IC_ROOT_KEY_VALUE_KEY: &str = "ic_root_key"; -const PUBLIC_ENV_VAR_NAME_PREFIX: &str = "PUBLIC_"; -const COOKIE_VALUES_SEPARATOR: &str = "&"; -// Builds the (URL-encoded) `ic_env` cookie value from the canister environment: -// `ic_root_key=` followed by each `PUBLIC_*` env var, joined by `&`. +// Builds the (URL-encoded) `ic_env` cookie value +// `ic_root_key=` and CANISTER_ID, joined by `&`. fn encoded_canister_env() -> String { - let mut values = vec![format!( - "{IC_ROOT_KEY_VALUE_KEY}={}", - hex::encode(root_key()) - )]; - - // BTreeMap keeps the env vars in a stable, sorted order. - let mut public_env_vars = BTreeMap::new(); - for i in 0..env_var_count() { - let name = env_var_name(i); - if name.starts_with(PUBLIC_ENV_VAR_NAME_PREFIX) { - let value = env_var_value(&name); - public_env_vars.insert(name, value); - } - } - values.extend(public_env_vars.iter().map(|(k, v)| format!("{k}={v}"))); + let values = [ + format!("ic_root_key={}", hex::encode(root_key())), + format!("CANISTER_ID={}", ic_cdk::api::canister_self()), + ]; - utf8_percent_encode(&values.join(COOKIE_VALUES_SEPARATOR), NON_ALPHANUMERIC).to_string() + utf8_percent_encode(&values.join("&"), NON_ALPHANUMERIC).to_string() } // Certification From 10aa7e38bb7ae19c6ad3d18281a2147348867f5a Mon Sep 17 00:00:00 2001 From: Raymond Khalife Date: Thu, 18 Jun 2026 21:15:46 -0400 Subject: [PATCH 42/43] fix the rust/bench --- rust/bench/Cargo.lock | 27 +++++++++++++++++++++++- rust/bench/bench.rs | 12 ++++++++--- rust/candid/fuzz/Cargo.lock | 35 ++++++++++++++++++++++++++++++-- rust/candid/src/pretty/candid.rs | 8 ++++++-- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/rust/bench/Cargo.lock b/rust/bench/Cargo.lock index cd34cf046..d6b0fc946 100644 --- a/rust/bench/Cargo.lock +++ b/rust/bench/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.100" @@ -162,6 +168,8 @@ dependencies = [ "binread", "byteorder", "candid_derive", + "foldhash", + "hashbrown 0.15.5", "hex", "ic_principal", "leb128", @@ -416,6 +424,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-array" version = "0.14.7" @@ -453,6 +467,17 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -530,7 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] diff --git a/rust/bench/bench.rs b/rust/bench/bench.rs index 8f5256acb..5aeb941c2 100644 --- a/rust/bench/bench.rs +++ b/rust/bench/bench.rs @@ -519,15 +519,21 @@ fn subtype_decode() -> BenchResult { // code path. See https://github.com/dfinity/candid/issues/603 #[bench(raw)] fn vec_service() -> BenchResult { - use candid::types::{Function, TypeEnv, TypeInner}; + use candid::types::{ArgType, Function, TypeEnv, TypeInner}; let mut config = DecoderConfig::new(); config.set_decoding_quota(COST); let method_type: candid::types::Type = TypeInner::Func(Function { modes: vec![], - args: vec![TypeInner::Text.into()], - rets: vec![TypeInner::Nat64.into()], + args: vec![ArgType { + name: None, + typ: TypeInner::Text.into(), + }], + rets: vec![ArgType { + name: None, + typ: TypeInner::Nat64.into(), + }], }) .into(); diff --git a/rust/candid/fuzz/Cargo.lock b/rust/candid/fuzz/Cargo.lock index c046c15f3..69cc5fbaf 100644 --- a/rust/candid/fuzz/Cargo.lock +++ b/rust/candid/fuzz/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.86" @@ -66,12 +72,14 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "candid" -version = "0.10.27" +version = "0.10.29" dependencies = [ "anyhow", "binread", "byteorder", "candid_derive", + "foldhash", + "hashbrown", "hex", "ic_principal", "leb128", @@ -97,7 +105,7 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.10.27" +version = "0.10.29" dependencies = [ "lazy_static", "proc-macro2", @@ -172,6 +180,18 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-array" version = "0.14.7" @@ -182,6 +202,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hex" version = "0.4.3" diff --git a/rust/candid/src/pretty/candid.rs b/rust/candid/src/pretty/candid.rs index f23ce0bc3..ddbaba342 100644 --- a/rust/candid/src/pretty/candid.rs +++ b/rust/candid/src/pretty/candid.rs @@ -200,8 +200,12 @@ fn pp_ty_with_doc<'a>(ty: &'a Type, doc: Option<&'a TypeDoc>) -> RcDoc<'a> { match ty.as_ref() { Record(ref fs) => { if ty.is_tuple() && !has_field_docs(doc) { - let tuple = - sep_enclose_space(fs.iter().map(|f| pp_ty_with_doc(&f.ty, None)), ";", "{", "}"); + let tuple = sep_enclose_space( + fs.iter().map(|f| pp_ty_with_doc(&f.ty, None)), + ";", + "{", + "}", + ); kwd("record").append(tuple) } else { kwd("record").append(pp_fields_with_doc(fs, false, doc)) From bab84c6ce2ff924134a89824dc057f4b1a1f8956 Mon Sep 17 00:00:00 2001 From: raymondk Date: Mon, 22 Jun 2026 21:40:14 -0400 Subject: [PATCH 43/43] Update CHANGELOG with breaking and non-breaking changes --- CHANGELOG.md | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bb9f8f5f..b575cc6bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## Unreleased + +### Candid + +* [BREAKING]: type representation was optimized to improve performance: + + In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. + + `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. + + The `args` and `rets` fields of the `candid::types::internal::Function` struct now are a `Vec` instead of `Vec`, to preserve names. + + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. + +* [BREAKING]: Removed the `candid::pretty::concat` function + + `candid::pretty::enclose` and `candid::pretty:enclose_space` don't collapse the separators on empty documents anymore + +* Non-breaking changes: + + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. + + The `JavaScript` `didc` target now exports its generated IDL type objects. + + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function alternatives to `idlFactory` and `init`). + +### candid_parser + +* Breaking changes: + + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. + + The `rets` field in `FuncType` now has type `Vec`. + +* Non-breaking changes: + + Supports parsing the arguments' names for `func` and `service` (init args). + ## 2026-05-27 ### Candid 0.10.29 @@ -103,31 +130,6 @@ * Non-breaking changes: + fix: escape `*/` to prevent premature JS doc comment termination -### Candid - -* [BREAKING]: type representation was optimized to improve performance: - + In `Type::Var(var)` `var` now has type `TypeKey` instead of `String`. Calling `var.as_str()` returns `&str` and `var.to_string()` returns a `String`. The string representation of indexed variables remains `table{index}` to maintain compatibility with previous versions. - + `TypeEnv` now contains a `HashMap` instead of `BTreeMap`. Code that relied on the iteration order of the map (e.g. `env.0.iter()`) should make use of the newly added `TypeEnv::to_sorted_iter()` method which returns types sorted by their keys. - + The `args` and `rets` fields of the `candid::types::internal::Function` struct now are a `Vec` instead of `Vec`, to preserve names. - + The `TypeInner::Class` variant now takes `Vec` instead of `Vec` as its first parameter, to preserve argument names. - -* [BREAKING]: Removed the `candid::pretty::concat` function - + `candid::pretty::enclose` and `candid::pretty:enclose_space` don't collapse the separators on empty documents anymore - -* Non-breaking changes: - + Added `pp_named_args`, `pp_named_init_args` in `pretty::candid` module. - + The `JavaScript` `didc` target now exports its generated IDL type objects. - + The `JavaScript` and `TypeScript` `didc` targets now export `idlService` and `idlInitArgs` (non-factory-function alternatives to `idlFactory` and `init`). - -### candid_parser - -* Breaking changes: - + The `args` field in both `FuncType` and `IDLInitArgs` now have type `Vec`. - + The `rets` field in `FuncType` now has type `Vec`. - -* Non-breaking changes: - + Supports parsing the arguments' names for `func` and `service` (init args). - ## 2025-10-26 ### Candid 0.10.20