diff --git a/.github/actions/build-test-wasm/action.yaml b/.github/actions/build-test-wasm/action.yaml index fe006065..6c8f589a 100644 --- a/.github/actions/build-test-wasm/action.yaml +++ b/.github/actions/build-test-wasm/action.yaml @@ -7,15 +7,11 @@ inputs: runs: using: 'composite' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + - uses: ./.github/actions/setup-wasm - run: yarn install shell: bash - - name: Install wasm-pack - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - shell: bash - name: Build WASM run: | mkdir -p ./prebuilds/${{ inputs.crate }} diff --git a/.github/actions/setup-wasm/action.yaml b/.github/actions/setup-wasm/action.yaml index f70dc23a..59e42031 100644 --- a/.github/actions/setup-wasm/action.yaml +++ b/.github/actions/setup-wasm/action.yaml @@ -3,8 +3,12 @@ description: Restore the Cargo cache and install the pinned WASM toolchain runs: using: composite steps: - - name: Add WASM target - run: rustup target add wasm32-unknown-unknown + - name: Install WASM Rust toolchain + run: | + rustup toolchain install "$(cat wasm-rust-toolchain)" \ + --profile minimal \ + --component rust-src \ + --target wasm32-unknown-unknown shell: bash - name: Restore Cargo build cache uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 @@ -16,11 +20,12 @@ runs: ~/.cargo/registry/index target key: >- - wasm-${{ runner.os }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml') }}-${{ + wasm-${{ runner.os }}-${{ + hashFiles('Cargo.lock', '*rust-toolchain*', '.cargo/config.toml', 'scripts/build-wasm.js') }}-${{ hashFiles('Cargo.toml', 'crates/**/Cargo.toml', 'crates/**/*.rs') }} restore-keys: >- wasm-${{ runner.os }}-${{ - hashFiles('Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml') }}- + hashFiles('Cargo.lock', '*rust-toolchain*', '.cargo/config.toml', 'scripts/build-wasm.js') }}- - name: Install wasm-pack env: WASM_PACK_SHA256: 278a8d668085821f4d1a637bd864f1713f872b0ae3a118c77562a308c0abfe8d diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f240bc04..a92f5be3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,3 +127,37 @@ jobs: uses: qard/heaviest-objects-in-the-universe@v1 with: github-token: ${{ secrets.GITHUB_TOKEN }} + + release-candidate: + if: github.event_name == 'pull_request' && github.base_ref == 'v0.x' + needs: + - lint + - package-size + - test-libdatadog + - test-libdatadog-windows + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: prebuilds + path: prebuilds + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: libdatadog-wasm + path: packages/libdatadog/wasm/dist + - id: candidate + run: | + TREE=$(git rev-parse 'HEAD^{tree}') + echo "$TREE" > release-candidate + echo "artifact-name=release-candidate-$TREE" >> "$GITHUB_OUTPUT" + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ steps.candidate.outputs.artifact-name }} + path: | + prebuilds + packages/libdatadog/wasm/dist + release-candidate + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 979959f9..74646044 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,7 @@ jobs: runs-on: ubuntu-latest outputs: npm-tag: ${{ steps.versions.outputs.npm-tag }} + tree: ${{ steps.versions.outputs.tree }} version: ${{ steps.versions.outputs.version }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -34,10 +35,65 @@ jobs: { echo "npm-tag=$NPM_TAG" + echo "tree=$(git rev-parse 'HEAD^{tree}')" echo "version=$VERSION" } >> "$GITHUB_OUTPUT" + resolve-release-candidate: + if: github.event_name == 'push' + needs: versions + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + pull-requests: read + outputs: + run-id: ${{ steps.candidate.outputs.run-id }} + steps: + - id: candidate + env: + ARTIFACT_NAME: release-candidate-${{ needs.versions.outputs.tree }} + GH_TOKEN: ${{ github.token }} + run: | + PULL=$( + gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" --jq ' + [ .[] | select( + .state == "closed" and + .merged_at != null and + .base.ref == "v0.x" + ) + ] + | if length == 1 then .[0] else error("expected one merged v0.x pull request") end + | [.merge_commit_sha, .head.repo.full_name] + | @tsv + ' + ) + IFS=$'\t' read -r MERGE_SHA HEAD_REPOSITORY <<< "$PULL" + + test "$MERGE_SHA" = "$GITHUB_SHA" + test "$HEAD_REPOSITORY" = "$GITHUB_REPOSITORY" + + for attempt in {1..90}; do + RUN_ID=$( + gh api "repos/$GITHUB_REPOSITORY/actions/artifacts?name=$ARTIFACT_NAME" --jq " + [ + .artifacts[] + | select(.expired == false) + ] + | max_by(.created_at).workflow_run.id // empty + " + ) + test -z "$RUN_ID" || break + if test "$attempt" -lt 90; then + sleep 10 + fi + done + test -n "$RUN_ID" + + echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT" + build-test-wasm: + if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest strategy: matrix: @@ -53,6 +109,7 @@ jobs: crate: '${{ matrix.crate }}' build-libdatadog-extras: + if: github.event_name == 'workflow_dispatch' uses: Datadog/action-prebuildify/.github/workflows/build.yml@main needs: build-test-wasm with: @@ -68,6 +125,7 @@ jobs: apt-get install -y autoconf automake libtool) || true build-libdatadog-wasm: + if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest needs: versions steps: @@ -87,6 +145,7 @@ jobs: path: packages/libdatadog/wasm/dist test-libdatadog-wasm: + if: github.event_name == 'workflow_dispatch' needs: build-libdatadog-wasm runs-on: ubuntu-latest strategy: @@ -107,6 +166,7 @@ jobs: test-libdatadog-windows: name: Test libdatadog WASM (Windows, Node ${{ matrix.node }}) + if: github.event_name == 'workflow_dispatch' needs: build-libdatadog-wasm runs-on: windows-latest strategy: @@ -127,10 +187,21 @@ jobs: - run: npm test --prefix packages/libdatadog publish: + if: >- + !cancelled() && + needs.versions.result == 'success' && + ((github.event_name == 'push' && + needs.resolve-release-candidate.result == 'success') || + (github.event_name == 'workflow_dispatch' && + needs.build-libdatadog-extras.result == 'success' && + needs.build-libdatadog-wasm.result == 'success' && + needs.test-libdatadog-wasm.result == 'success' && + needs.test-libdatadog-windows.result == 'success')) runs-on: ubuntu-latest needs: - build-libdatadog-extras - build-libdatadog-wasm + - resolve-release-candidate - test-libdatadog-wasm - test-libdatadog-windows - versions @@ -138,6 +209,7 @@ jobs: name: npm url: https://npmjs.com/package/@datadog/libdatadog permissions: + actions: read id-token: write # Required for OIDC contents: read steps: @@ -156,10 +228,19 @@ jobs: node-version: '24' registry-url: https://registry.npmjs.org - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + if: github.event_name == 'push' + with: + name: release-candidate-${{ needs.versions.outputs.tree }} + path: . + github-token: ${{ github.token }} + run-id: ${{ needs.resolve-release-candidate.outputs.run-id }} + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + if: github.event_name == 'workflow_dispatch' with: name: prebuilds path: prebuilds - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + if: github.event_name == 'workflow_dispatch' with: name: libdatadog-wasm path: packages/libdatadog/wasm/dist diff --git a/Cargo.lock b/Cargo.lock index ab6b5614..b5bd01cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,12 +179,6 @@ dependencies = [ "crossbeam-channel", ] -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cc" version = "1.4.4" @@ -281,9 +275,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.6.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" dependencies = [ "unicode-segmentation", ] @@ -370,13 +364,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.9" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.119", -] +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" [[package]] name = "darling" @@ -605,9 +595,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -636,9 +626,9 @@ checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -653,13 +643,13 @@ checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] @@ -676,9 +666,9 @@ checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -1215,13 +1205,11 @@ version = "0.1.0" dependencies = [ "anyhow", "bytes", - "futures-core", "http 1.5.0", "js-sys", "libdd-capabilities", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", ] [[package]] @@ -1686,29 +1674,21 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" dependencies = [ "cfg-if", "windows-link 0.2.1", ] -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - [[package]] name = "library-config" version = "0.2.0" dependencies = [ - "anyhow", "getrandom 0.2.17", "libdd-library-config", "wasm-bindgen", - "wasm-bindgen-test", ] [[package]] @@ -1771,16 +1751,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "minicov" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3aa3aa12b448ac225b3102217d1ac5cc717908f02722926524b0599c933c7a0" -dependencies = [ - "cc", - "walkdir", -] - [[package]] name = "miniz_oxide" version = "0.9.1" @@ -1814,26 +1784,34 @@ dependencies = [ [[package]] name = "napi" -version = "2.16.17" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +checksum = "58c5f4d5375213fdb7be2655e152386e82f026f9a5ba36a75556e11359aafe09" dependencies = [ "bitflags 2.13.1", "ctor", - "napi-derive", + "futures", + "libc", + "napi-build", "napi-sys", - "once_cell", + "nohash-hasher", + "rustc-hash", "serde", "serde_json", ] +[[package]] +name = "napi-build" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60fdf9b392c50e7c4170fa633bd909490ed7835cea4c046776d1a4dd8d2ae0ab" + [[package]] name = "napi-derive" -version = "2.16.13" +version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +checksum = "0fa55ea69990c90b888e9e77044410e304ce7f35de599dc6d0b5c1923d2e59af" dependencies = [ - "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", @@ -1843,12 +1821,11 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "1.0.75" +version = "6.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +checksum = "df4056ac7c18e4438ccf0edaed4340ca0d269278c8ec19284f7b23cb039fd0ae" dependencies = [ "convert_case", - "once_cell", "proc-macro2", "quote", "syn 2.0.119", @@ -1856,9 +1833,9 @@ dependencies = [ [[package]] name = "napi-sys" -version = "2.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +checksum = "85fbf1fa9f1babfe396d74bbbf52b3643770243e8f5b0b46715d4caf7f0dfc9a" dependencies = [ "libloading", ] @@ -1889,13 +1866,10 @@ dependencies = [ ] [[package]] -name = "nu-ansi-term" -version = "0.50.3" +name = "nohash-hasher" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "num-conv" @@ -1921,7 +1895,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -2089,12 +2062,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - [[package]] name = "openssl-probe" version = "0.1.6" @@ -2183,7 +2150,6 @@ dependencies = [ "uuid", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", ] [[package]] @@ -2247,7 +2213,6 @@ dependencies = [ name = "process-discovery" version = "0.1.0" dependencies = [ - "anyhow", "libdd-library-config", "libdd-trace-protobuf", "napi", @@ -2391,7 +2356,6 @@ dependencies = [ "serde_json", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", ] [[package]] @@ -2639,9 +2603,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2679,22 +2643,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] @@ -3276,45 +3240,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-bindgen-test" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "895a2607575412a4eda1df892084a375ea10dfeadc4d7d2ab87b854e4ddc7ba1" -dependencies = [ - "async-trait", - "cast", - "js-sys", - "libm", - "minicov", - "nu-ansi-term", - "num-traits", - "oorandom", - "serde", - "serde_json", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test-macro", - "wasm-bindgen-test-shared", -] - -[[package]] -name = "wasm-bindgen-test-macro" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288cb0ebe215033bf949ae1fd046726daa4c32a157f24b9dc6ac387a52aa759" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "wasm-bindgen-test-shared" -version = "0.2.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ff1c1b360982e93b6d8ea9c04836f71dba0817a16f91e229cf3a51bdd9d987" - [[package]] name = "web-time" version = "1.1.0" diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 3e05281c..722cea6a 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -19,7 +19,6 @@ bs58,https://github.com/Nullus157/bs58-rs,MIT OR Apache-2.0,The bs58 Authors bumpalo,https://github.com/fitzgen/bumpalo,MIT OR Apache-2.0,Nick Fitzgerald bytes,https://github.com/tokio-rs/bytes,MIT,"Carl Lerche , Sean McArthur " cadence,https://github.com/56quarters/cadence,Apache-2.0 OR MIT,Nick Pillitteri -cast,https://github.com/japaric/cast.rs,MIT OR Apache-2.0,Jorge Aparicio cc,https://github.com/rust-lang/cc-rs,MIT OR Apache-2.0,Alex Crichton cesu8,https://github.com/emk/cesu8-rs,Apache-2.0 OR MIT,Eric Kidd cfg-if,https://github.com/rust-lang/cfg-if,MIT OR Apache-2.0,Alex Crichton @@ -135,7 +134,6 @@ libdd-trace-stats,https://github.com/DataDog/libdatadog/tree/main/libdd-trace-st libdd-trace-utils,https://github.com/DataDog/libdatadog/tree/main/libdd-trace-utils,Apache-2.0,The libdd-trace-utils Authors libdd-tuf,https://github.com/theupdateframework/rust-tuf,MIT OR Apache-2.0,"heartsucker , Erick Tryzelaar " libloading,https://github.com/nagisa/rust_libloading,ISC,Simonas Kazlauskas -libm,https://github.com/rust-lang/compiler-builtins,MIT,"Alex Crichton , Amanieu d'Antras , Jorge Aparicio , Trevor Gross " linux-raw-sys,https://github.com/sunfishcode/linux-raw-sys,Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,Dan Gohman litemap,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers log,https://github.com/rust-lang/log,MIT OR Apache-2.0,The Rust Project Developers @@ -144,7 +142,6 @@ memchr,https://github.com/BurntSushi/memchr,Unlicense OR MIT,"Andrew Gallant , Simonas Kazlauskas " memmap2,https://github.com/RazrFalcon/memmap2-rs,MIT OR Apache-2.0,"Dan Burkert , Yevhenii Reizner , The Contributors" memoffset,https://github.com/Gilnaa/memoffset,MIT,Gilad Naaman -minicov,https://github.com/Amanieu/minicov,Apache-2.0 OR MIT,Amanieu d'Antras miniz_oxide,https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide,MIT OR Zlib OR Apache-2.0,"Frommi , oyvindln , Rich Geldreich richgel99@gmail.com" mio,https://github.com/tokio-rs/mio,MIT,"Carl Lerche , Thomas de Zeeuw , Tokio Contributors " msvc-demangler,https://github.com/mstange/msvc-demangler-rust,MIT OR NCSA,"Markus Stange , Jeff Muizelaar " @@ -153,7 +150,6 @@ napi-derive,https://github.com/napi-rs/napi-rs,MIT,"LongYinan nix,https://github.com/nix-rust/nix,MIT,The nix-rust Project Developers -nu-ansi-term,https://github.com/nushell/nu-ansi-term,MIT,"ogham@bsago.me, Ryan Scheel (Havvy) , Josh Triplett , The Nushell Project Developers" num-conv,https://github.com/jhpratt/num-conv,MIT OR Apache-2.0,Jacob Pratt num-derive,https://github.com/rust-num/num-derive,MIT OR Apache-2.0,The Rust Project Developers num-traits,https://github.com/rust-num/num-traits,MIT OR Apache-2.0,The Rust Project Developers @@ -172,7 +168,6 @@ objc2-quartz-core,https://github.com/madsmtm/objc2,Zlib OR Apache-2.0 OR MIT,The objc2-ui-kit,https://github.com/madsmtm/objc2,Zlib OR Apache-2.0 OR MIT,The objc2-ui-kit Authors objc2-user-notifications,https://github.com/madsmtm/objc2,Zlib OR Apache-2.0 OR MIT,The objc2-user-notifications Authors once_cell,https://github.com/matklad/once_cell,MIT OR Apache-2.0,Aleksey Kladov -oorandom,https://hg.sr.ht/~icefox/oorandom,MIT,Simon Heath openssl-probe,https://github.com/alexcrichton/openssl-probe,MIT OR Apache-2.0,Alex Crichton os_info,https://github.com/stanislav-tkach/os_info,MIT,"Jan Schulte , Stanislav Tkach " page_size,https://github.com/Elzair/page_size_rs,MIT OR Apache-2.0,Philip Woods @@ -286,8 +281,6 @@ wasm-bindgen-futures,https://github.com/wasm-bindgen/wasm-bindgen/tree/master/cr wasm-bindgen-macro,https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro,MIT OR Apache-2.0,The wasm-bindgen Developers wasm-bindgen-macro-support,https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro-support,MIT OR Apache-2.0,The wasm-bindgen Developers wasm-bindgen-shared,https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/shared,MIT OR Apache-2.0,The wasm-bindgen Developers -wasm-bindgen-test-macro,https://github.com/wasm-bindgen/wasm-bindgen,MIT OR Apache-2.0,The wasm-bindgen Developers -wasm-bindgen-test-shared,https://github.com/rustwasm/wasm-bindgen/tree/master/crates/test-shared,MIT OR Apache-2.0,The wasm-bindgen Developers wasm-encoder,https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-encoder,Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,Nick Fitzgerald wasm-metadata,https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-metadata,Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,The wasm-metadata Authors wasmparser,https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasmparser,Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,Yury Delendik diff --git a/crates/capabilities/Cargo.toml b/crates/capabilities/Cargo.toml index 28999d39..57df8ee3 100644 --- a/crates/capabilities/Cargo.toml +++ b/crates/capabilities/Cargo.toml @@ -13,9 +13,5 @@ wasm-bindgen-futures = "0.4" js-sys = "0.3" http = "1" bytes = "1.4" -futures-core = "0.3" anyhow = "1" libdd-capabilities = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0" } - -[dev-dependencies] -wasm-bindgen-test = "0.3" diff --git a/crates/capabilities/src/http_transport.js b/crates/capabilities/src/http_transport.js index 0fd60257..97f42859 100644 --- a/crates/capabilities/src/http_transport.js +++ b/crates/capabilities/src/http_transport.js @@ -24,7 +24,8 @@ let storage = f => f() // The second alternative is the PCF / Garden regexp; no suffix ($) to avoid // matching pod UIDs. See // https://github.com/DataDog/datadog-agent/blob/7.40.x/pkg/util/cgroups/reader.go#L50 -const uuidSource = String.raw`[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|[0-9a-f]{8}(?:-[0-9a-f]{4}){4}$` +const uuidSource = '[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|' + + '[0-9a-f]{8}(?:-[0-9a-f]{4}){4}$' const containerSource = '[0-9a-f]{64}' const taskSource = String.raw`[0-9a-f]{32}-\d+` const lineReg = /^(\d+):([^:]*):(.+)$/m @@ -113,12 +114,15 @@ function applyEntityHeaders (headView, entity = getEntityHeaders()) { // `req._header`: that internal is undocumented and Bun's `node:http` ignores it, // so under Bun the request went out as `POST /` with no headers and the agent // dropped it. Header names/values are ASCII (latin1 round-trips losslessly). -function parseRequestHead (headBuf) { - const head = Buffer.from(headBuf).toString('latin1') +/** + * @param {Buffer} headBuffer + */ +function parseRequestHead (headBuffer) { + const head = headBuffer.toString('latin1') const term = head.indexOf('\r\n\r\n') const lines = (term === -1 ? head : head.slice(0, term)).split('\r\n') // Request line: `METHOD request-target HTTP/1.1` (no spaces in the target). - const [method, path] = lines[0].split(' ') + const [method, path] = lines[0].split(' ', 2) const headers = {} for (let i = 1; i < lines.length; i++) { const colon = lines[i].indexOf(':') @@ -189,6 +193,44 @@ module.exports.httpRequest = function (host, port, isHttps, socketPath, connecti function attempt () { return new Promise((resolve, reject) => { + let chunks + let response + let settled = false + + /** @param {Error} [error] */ + function settle (error) { + if (settled) return + settled = true + + if (error) { + reject(error) + return + } + + const body = Buffer.concat(chunks) + if (responseHeaderObserver) { + try { + responseHeaderObserver(response.rawHeaders) + } catch (error) { + // Only read `error.message` (a string) rather than stringifying an + // arbitrary thrown value, so a hostile/throwing toString on the + // error can't turn the log line into its own failure path. + process.stderr.write('responseHeaderObserver error: ' + (error && error.message) + '\n') + } + } + resolve([ + response.statusCode, + response.rawHeaders, + // Buffer is a Uint8Array with exact byteOffset and byteLength. + // Rust copies it into Bytes before this response is released. + body, + ]) + } + + function abortResponse () { + settle(new Error('response aborted')) + } + storage(() => { // wasm_memory.buffer is replaced each time WebAssembly.Memory grows, so // the views must be recreated on every attempt against the current buffer. @@ -212,34 +254,18 @@ module.exports.httpRequest = function (host, port, isHttps, socketPath, connecti ? { socketPath, method, path, headers } : { host, port, method, path, headers } if (!connectionPooling) requestOptions.agent = false - const req = transport.request(requestOptions, (res) => { - const chunks = [] + /** @param {import('node:http').IncomingMessage} res */ + function handleResponse (res) { + response = res + chunks = [] res.on('data', chunk => chunks.push(chunk)) - res.on('end', () => { - const body = Buffer.concat(chunks) - if (responseHeaderObserver) { - try { - responseHeaderObserver(res.rawHeaders) - } catch (error) { - // Only read `err.message` (a string) rather than stringifying an - // arbitrary thrown value, so a hostile/throwing toString on the - // error can't turn the log line into its own failure path. - process.stderr.write('responseHeaderObserver error: ' + (error && error.message) + '\n') - } - } - resolve([ - res.statusCode, - res.rawHeaders, - // Copy the exact body bytes. `body` is a Buffer from Buffer.concat, - // which for small payloads is a view into Node's shared pool, so - // `body.buffer` is the whole pool — slicing by offset/length (via - // the Uint8Array(typedArray) copy ctor) is required to avoid - // handing the Rust side unrelated pooled memory. - new Uint8Array(body), - ]) - }) - }) - req.on('error', reject) + res.once('end', settle) + res.once('aborted', abortResponse) + res.once('error', settle) + } + + const req = transport.request(requestOptions, handleResponse) + req.once('error', settle) // The request head (method/path/headers) was supplied via requestOptions // above; just write the stable Node-owned body. (No `req._header` @@ -248,7 +274,7 @@ module.exports.httpRequest = function (host, port, isHttps, socketPath, connecti req.write(body) req.end() } catch (error) { - reject(error) + settle(error) } }) }) diff --git a/crates/crashtracker/Cargo.toml b/crates/crashtracker/Cargo.toml index 25c6a709..1044035c 100644 --- a/crates/crashtracker/Cargo.toml +++ b/crates/crashtracker/Cargo.toml @@ -15,7 +15,7 @@ path = "src/bin/receiver.rs" [dependencies] anyhow = "1" libdd-crashtracker = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0" } -napi = { version = "2", features = ["serde-json"] } -napi-derive = { version = "2", default-features = false } +napi = { version = "3", features = ["serde-json"] } +napi-derive = { version = "3", default-features = false } rustls = { version = "*", default-features = false, features = ["aws-lc-rs"] } serde_json = "1" diff --git a/crates/crashtracker/src/lib.rs b/crates/crashtracker/src/lib.rs index eb906969..b39dcbae 100644 --- a/crates/crashtracker/src/lib.rs +++ b/crates/crashtracker/src/lib.rs @@ -1,4 +1,4 @@ -use napi::{Env, JsUnknown}; +use napi::{Env, Unknown}; use napi_derive::napi; mod unhandled_exception; @@ -22,9 +22,9 @@ fn apply_default_signals( #[napi] pub fn init( env: Env, - config: JsUnknown, - receiver_config: JsUnknown, - metadata: JsUnknown, + config: Unknown, + receiver_config: Unknown, + metadata: Unknown, ) -> napi::Result<()> { let config: libdd_crashtracker::CrashtrackerConfiguration = env.from_js_value(config)?; let receiver_config = env.from_js_value(receiver_config)?; @@ -38,7 +38,7 @@ pub fn init( } #[napi] -pub fn update_config(env: Env, config: JsUnknown) -> napi::Result<()> { +pub fn update_config(env: Env, config: Unknown) -> napi::Result<()> { let config: libdd_crashtracker::CrashtrackerConfiguration = env.from_js_value(config)?; let config = apply_default_signals(config); @@ -49,7 +49,7 @@ pub fn update_config(env: Env, config: JsUnknown) -> napi::Result<()> { } #[napi] -pub fn update_metadata(env: Env, metadata: JsUnknown) -> napi::Result<()> { +pub fn update_metadata(env: Env, metadata: Unknown) -> napi::Result<()> { let metadata = env.from_js_value(metadata)?; libdd_crashtracker::update_metadata(metadata).unwrap(); diff --git a/crates/crashtracker/src/unhandled_exception.rs b/crates/crashtracker/src/unhandled_exception.rs index 64dd8fcb..1a28e786 100644 --- a/crates/crashtracker/src/unhandled_exception.rs +++ b/crates/crashtracker/src/unhandled_exception.rs @@ -1,8 +1,9 @@ -use napi::{Env, JsFunction, JsObject, JsUnknown}; +use napi::bindgen_prelude::{Function, JsObjectValue, Object}; +use napi::{Env, JsValue, Unknown}; use napi_derive::napi; -fn get_optional_string_property(obj: &JsObject, key: &str) -> napi::Result> { - match obj.get_named_property::(key) { +fn get_optional_string_property(obj: &Object, key: &str) -> napi::Result> { + match obj.get_named_property::(key) { Ok(val) => { use napi::ValueType; if val.get_type()? == ValueType::String { @@ -80,21 +81,25 @@ fn parse_location(location: &str, frame: &mut libdd_crashtracker::StackFrame) { } } -fn is_error_instance(env: &Env, value: &JsUnknown) -> napi::Result { +fn is_error_instance(env: &Env, value: &Unknown) -> napi::Result { + if value.is_error()? { + return Ok(true); + } + let global = env.get_global()?; - let error_ctor: JsFunction = global.get_named_property("Error")?; + let error_ctor: Function<'_, (), Unknown<'_>> = global.get_named_property("Error")?; value.instanceof(error_ctor) } -fn stringify_js_value(value: JsUnknown) -> napi::Result { +fn stringify_js_value(value: Unknown) -> napi::Result { let s = value.coerce_to_string()?.into_utf8()?; Ok(s.as_str()?.to_owned()) } -fn report_unhandled(env: &Env, error: JsUnknown, fallback_type: &str) -> napi::Result<()> { +fn report_unhandled(env: &Env, error: Unknown, fallback_type: &str) -> napi::Result<()> { let is_error = is_error_instance(env, &error)?; let (exception_type, exception_message, stacktrace) = if is_error { - let error_obj: JsObject = error.coerce_to_object()?; + let error_obj: Object = error.coerce_to_object()?; let name = get_optional_string_property(&error_obj, "name")?; let message = get_optional_string_property(&error_obj, "message")?; let stack_string = get_optional_string_property(&error_obj, "stack")?; @@ -128,7 +133,7 @@ fn report_unhandled(env: &Env, error: JsUnknown, fallback_type: &str) -> napi::R #[napi] pub fn report_uncaught_exception_monitor( env: Env, - error: JsUnknown, + error: Unknown, origin: String, ) -> napi::Result<()> { report_unhandled(&env, error, &origin) diff --git a/crates/libdatadog-wasm/src/data_pipeline/mod.rs b/crates/libdatadog-wasm/src/data_pipeline/mod.rs index fd7c58b4..51e40831 100644 --- a/crates/libdatadog-wasm/src/data_pipeline/mod.rs +++ b/crates/libdatadog-wasm/src/data_pipeline/mod.rs @@ -5,8 +5,9 @@ use std::rc::Rc; use std::time::Duration; use bytes::Bytes; +use futures::channel::oneshot; use futures::future::{AbortHandle, Abortable}; -use js_sys::{Array, Function, Object, Promise, Reflect, Uint8Array}; +use js_sys::{Array, Function, Object, Reflect, Uint8Array}; use libdatadog_data_pipeline::{ send_agentless_v04, AgentlessTraceConfig, ObfuscationConfig, SendAgentlessV04Error, TracerMetadata, DEFAULT_AGENTLESS_TIMEOUT, @@ -20,7 +21,7 @@ use libdd_trace_obfuscation::obfuscation_config::{ use libdd_trace_obfuscation::replacer::ReplaceRule; use libdd_trace_obfuscation::sql::{SqlObfuscateConfig, SqlObfuscationMode}; use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::JsFuture; +use wasm_bindgen_futures::spawn_local; struct AgentlessExporterOptions { endpoint: String, @@ -76,13 +77,23 @@ impl HttpClientCapability for HostCapabilities { ) -> Result, HttpError> { let id = self.next_call_id(); let plan = request_value(id, request)?; + let (sender, receiver) = oneshot::channel(); + let complete = Closure::once(move |error: JsValue, response: JsValue| { + let result = if error.is_undefined() { + Ok(response) + } else { + Err(error) + }; + let _ = sender.send(result); + }); let mut guard = CancelGuard::new(id, self.cancel_request.clone()); - let promise = self - .request - .call1(&JsValue::UNDEFINED, &plan) - .map(|value| Promise::resolve(&value)) + self.request + .call2(&JsValue::UNDEFINED, &plan, complete.as_ref()) + .map_err(network_error)?; + let response = receiver + .await + .map_err(|_| callback_dropped("request"))? .map_err(network_error)?; - let response = JsFuture::from(promise).await.map_err(network_error)?; guard.disarm(); let status = required_number(&response, "status")?; @@ -104,13 +115,22 @@ impl SleepCapability for HostCapabilities { async fn sleep(&self, duration: Duration) { let id = self.next_call_id(); let milliseconds = duration_millis(duration); + let (sender, receiver) = oneshot::channel(); + let complete = Closure::once(move || { + let _ = sender.send(()); + }); let mut guard = CancelGuard::new(id, self.cancel_sleep.clone()); - if let Ok(value) = self.sleep.call2( - &JsValue::UNDEFINED, - &JsValue::from_f64(f64::from(id)), - &JsValue::from_f64(f64::from(milliseconds)), - ) { - let _ = JsFuture::from(Promise::resolve(&value)).await; + if self + .sleep + .call3( + &JsValue::UNDEFINED, + &JsValue::from_f64(f64::from(id)), + &JsValue::from_f64(f64::from(milliseconds)), + complete.as_ref(), + ) + .is_ok() + { + let _ = receiver.await; } guard.disarm(); } @@ -148,8 +168,8 @@ impl Drop for CancelGuard { #[wasm_bindgen] pub struct AgentlessExporter { - metadata: TracerMetadata, - config: AgentlessTraceConfig, + metadata: Rc, + config: Rc, capabilities: HostCapabilities, in_flight: Rc>>, next_operation_id: Cell, @@ -212,8 +232,8 @@ impl AgentlessExporter { }; Ok(Self { - metadata, - config, + metadata: Rc::new(metadata), + config: Rc::new(config), capabilities, in_flight: Rc::new(RefCell::new(HashMap::new())), next_operation_id: Cell::new(1), @@ -221,23 +241,28 @@ impl AgentlessExporter { } #[wasm_bindgen(js_name = sendV04)] - pub async fn send_v04(&self, payload: &[u8]) -> Result<(), JsValue> { + pub fn send_v04(&self, payload: Vec, done: Function) { let operation_id = self.next_operation_id.get(); self.next_operation_id.set(operation_id.wrapping_add(1)); let (abort, registration) = AbortHandle::new_pair(); self.in_flight.borrow_mut().insert(operation_id, abort); - let _guard = OperationGuard { - id: operation_id, - in_flight: self.in_flight.clone(), - }; - let send = - send_agentless_v04(&self.capabilities, payload, &self.metadata, &self.config, false); - - match Abortable::new(send, registration).await { - Ok(Ok(_)) => Ok(()), - Ok(Err(error)) => Err(send_error(error)), - Err(_) => Err(JsValue::from_str("data-pipeline export was cancelled")), - } + let capabilities = self.capabilities.clone(); + let config = self.config.clone(); + let in_flight = self.in_flight.clone(); + let metadata = self.metadata.clone(); + spawn_local(async move { + let _guard = OperationGuard { + id: operation_id, + in_flight, + }; + let send = send_agentless_v04(&capabilities, &payload, &metadata, &config, false); + let error = match Abortable::new(send, registration).await { + Ok(Ok(_)) => JsValue::UNDEFINED, + Ok(Err(error)) => send_error(error), + Err(_) => JsValue::from_str("data-pipeline export was cancelled"), + }; + let _ = done.call1(&JsValue::UNDEFINED, &error); + }); } #[wasm_bindgen(js_name = cancelAll)] @@ -311,6 +336,10 @@ fn network_error(error: JsValue) -> HttpError { HttpError::Network(anyhow::anyhow!(js_error_message(error))) } +fn callback_dropped(name: &str) -> HttpError { + HttpError::Network(anyhow::anyhow!("JavaScript {name} callback was dropped")) +} + fn send_error(error: SendAgentlessV04Error) -> JsValue { JsValue::from_str(&error.to_string()) } diff --git a/crates/library_config/Cargo.toml b/crates/library_config/Cargo.toml index 81740f74..efd0a6a8 100644 --- a/crates/library_config/Cargo.toml +++ b/crates/library_config/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" crate-type = ["cdylib", "rlib"] [dependencies] -anyhow = "1" libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0" } wasm-bindgen = "0.2.100" @@ -15,8 +14,5 @@ wasm-bindgen = "0.2.100" [package.metadata.wasm-pack.profile.release] wasm-opt = ["-O", "--enable-bulk-memory"] -[dev-dependencies] -wasm-bindgen-test = "0.3.50" - [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/crates/pipeline/Cargo.toml b/crates/pipeline/Cargo.toml index d7deb862..4f388182 100644 --- a/crates/pipeline/Cargo.toml +++ b/crates/pipeline/Cargo.toml @@ -25,9 +25,6 @@ console_error_panic_hook = "0.1" getrandom = { version = "0.2", features = ["js"] } uuid = { version = "1", features = ["js"] } -[dev-dependencies] -wasm-bindgen-test = "0.3" - # The pipeline wasm uses post-MVP features (bulk-memory for change-buffer # copies, sign-extension, etc.) that the wasm-opt bundled with wasm-pack does # not enable by default. Pass --all-features so wasm-opt validation passes diff --git a/crates/process_discovery/Cargo.toml b/crates/process_discovery/Cargo.toml index d46b257b..f75fa61d 100644 --- a/crates/process_discovery/Cargo.toml +++ b/crates/process_discovery/Cargo.toml @@ -7,9 +7,8 @@ edition = "2018" crate-type = ["cdylib", "rlib"] [dependencies] -anyhow = "1" libdd-library-config = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0", features = ["otel-thread-ctx"] } libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog.git", tag = "v43.0.0" } -napi = { version = "2" } -napi-derive = { version = "2", default-features = false } +napi = { version = "3" } +napi-derive = { version = "3", default-features = false } diff --git a/crates/process_discovery/src/lib.rs b/crates/process_discovery/src/lib.rs index 6e042e8f..a9509ef3 100644 --- a/crates/process_discovery/src/lib.rs +++ b/crates/process_discovery/src/lib.rs @@ -17,7 +17,6 @@ impl NapiAnonymousFileHandle {} /// exactly one of `string_value` / `int_value` — the other variants of OTel's /// `AnyValue` (bool, double, bytes, array, kvlist) are not yet exposed. /// Passing both set or neither set is rejected as invalid input. -#[derive(Clone)] #[napi(object)] pub struct ExtraAttribute { pub key: String, @@ -29,7 +28,6 @@ pub struct ExtraAttribute { /// OTel process context. When present on a [`TracerMetadata`], drives the /// `threadlocal.*` block in the emitted process context; when absent, no such /// block is emitted. -#[derive(Clone)] #[napi(object)] pub struct ThreadLocalMetadata { /// Ordered list of attribute key names for thread-level OTEP-4947 context @@ -50,7 +48,7 @@ pub struct ThreadLocalMetadata { pub extra_attributes: Vec, } -#[napi(constructor)] +#[napi(object)] pub struct TracerMetadata { pub runtime_id: Option, pub tracer_version: String, @@ -61,21 +59,53 @@ pub struct TracerMetadata { pub process_tags: Option, pub container_id: Option, /// Optional thread-level context metadata; see [`ThreadLocalMetadata`]. - /// `null`/omitted (the default) disables the `threadlocal.*` block in the - /// emitted OTel process context entirely. + /// Omitted or `undefined` disables the `threadlocal.*` block in the emitted + /// OTel process context entirely. pub threadlocal_metadata: Option, } -fn convert_extra_attribute(ea: &ExtraAttribute) -> napi::Result<(String, any_value::Value)> { - let value = match (&ea.string_value, ea.int_value) { - (Some(s), None) => any_value::Value::StringValue(s.clone()), - (None, Some(i)) => any_value::Value::IntValue(i), +/// Builds a metadata object for callers that use the positional constructor. +#[allow(clippy::too_many_arguments)] +#[napi(js_name = "TracerMetadata")] +pub fn legacy_tracer_metadata( + runtime_id: Option, + tracer_version: String, + hostname: String, + service_name: Option, + service_env: Option, + service_version: Option, + process_tags: Option, + container_id: Option, + threadlocal_metadata: Option, +) -> TracerMetadata { + TracerMetadata { + runtime_id, + tracer_version, + hostname, + service_name, + service_env, + service_version, + process_tags, + container_id, + threadlocal_metadata, + } +} + +fn convert_extra_attribute(attribute: ExtraAttribute) -> napi::Result<(String, any_value::Value)> { + let ExtraAttribute { + key, + string_value, + int_value, + } = attribute; + let value = match (string_value, int_value) { + (Some(value), None) => any_value::Value::StringValue(value), + (None, Some(value)) => any_value::Value::IntValue(value), (Some(_), Some(_)) => { return Err(Error::new( Status::InvalidArg, format!( "ExtraAttribute {:?}: exactly one of stringValue / intValue must be set, both are", - ea.key, + key, ), )); } @@ -84,53 +114,52 @@ fn convert_extra_attribute(ea: &ExtraAttribute) -> napi::Result<(String, any_val Status::InvalidArg, format!( "ExtraAttribute {:?}: exactly one of stringValue / intValue must be set, neither is", - ea.key, + key, ), )); } }; - Ok((ea.key.clone(), value)) + Ok((key, value)) } fn convert_threadlocal_metadata( - tlm: &ThreadLocalMetadata, + metadata: ThreadLocalMetadata, ) -> napi::Result { Ok(tracer_metadata::ThreadLocalMetadata { - attribute_keys: tlm.attribute_keys.clone(), - schema_version: tlm.schema_version.clone(), - extra_attributes: tlm + attribute_keys: metadata.attribute_keys, + schema_version: metadata.schema_version, + extra_attributes: metadata .extra_attributes - .iter() + .into_iter() .map(convert_extra_attribute) .collect::>()?, }) } #[napi] -pub fn store_metadata(data: &TracerMetadata) -> napi::Result { - let res = tracer_metadata::store_tracer_metadata(&tracer_metadata::TracerMetadata{ +pub fn store_metadata(data: TracerMetadata) -> napi::Result { + let result = tracer_metadata::store_tracer_metadata(&tracer_metadata::TracerMetadata { schema_version: 1, - runtime_id: data.runtime_id.clone(), + runtime_id: data.runtime_id, tracer_language: String::from("nodejs"), - tracer_version: data.tracer_version.clone(), - hostname: data.hostname.clone(), - service_name: data.service_name.clone(), - service_env: data.service_env.clone(), - service_version: data.service_version.clone(), - process_tags: data.process_tags.clone(), - container_id: data.container_id.clone(), + tracer_version: data.tracer_version, + hostname: data.hostname, + service_name: data.service_name, + service_env: data.service_env, + service_version: data.service_version, + process_tags: data.process_tags, + container_id: data.container_id, threadlocal_metadata: data .threadlocal_metadata - .as_ref() .map(convert_threadlocal_metadata) .transpose()?, }); - match res { - Ok(handle) => Ok(NapiAnonymousFileHandle{ _internal: handle }), - Err(e) => { - let err_msg = format!("Failed to store the tracer configuration: {:?}", e); - Err(Error::new(Status::GenericFailure, err_msg)) + match result { + Ok(handle) => Ok(NapiAnonymousFileHandle { _internal: handle }), + Err(error) => { + let error_message = format!("Failed to store the tracer configuration: {:?}", error); + Err(Error::new(Status::GenericFailure, error_message)) } } } diff --git a/crates/remote_config/Cargo.toml b/crates/remote_config/Cargo.toml index 649144fb..431bf52b 100644 --- a/crates/remote_config/Cargo.toml +++ b/crates/remote_config/Cargo.toml @@ -22,6 +22,3 @@ console_error_panic_hook = "0.1" [package.metadata.wasm-pack.profile.release] wasm-opt = ["-O", "--all-features"] - -[dev-dependencies] -wasm-bindgen-test = "0.3" diff --git a/eslint.config.js b/eslint.config.js index daf48dea..890ee8ba 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -39,8 +39,32 @@ module.exports = [ }], 'n/no-process-exit': 'off', // Duplicate of unicorn/no-process-exit 'prefer-const': 'error', + + // Match the deliberate exceptions in dd-trace-js. The remaining recommended rules stay enabled. + 'unicorn/consistent-boolean-name': 'off', // Would rename public API and config booleans + 'unicorn/filename-case': ['error', { + case: 'kebabCase', + // The WASM package name must match the Rust crate name. + ignore: ['^library_config$'], + }], + 'unicorn/name-replacements': 'off', // Naming churn + 'unicorn/no-break-in-nested-loop': 'off', // Conflicts with performance-oriented loops + 'unicorn/no-negated-array-predicate': 'off', // Predicate inversion is harder to read + 'unicorn/no-return-array-push': 'off', // Questionable benefit + 'unicorn/no-this-outside-of-class': 'off', // Object methods and callback APIs can bind `this` + 'unicorn/no-top-level-assignment-in-function': 'off', // Module-level singletons are assigned from functions + 'unicorn/no-undeclared-class-members': 'off', // Field declarations can change object shape + 'unicorn/prefer-await': 'off', // Production code uses callbacks and synchronous patterns + 'unicorn/prefer-minimal-ternary': 'off', // Conflicts with restricted syntax in consumers 'unicorn/prefer-module': 'off', // We use CJS + 'unicorn/prefer-number-is-safe-integer': 'off', // Number.isInteger() can be intentional + 'unicorn/prefer-private-class-fields': 'off', // Existing underscore fields can cross module boundaries + 'unicorn/prefer-promise-with-resolvers': 'off', // Promise.withResolvers() requires Node.js 22 + 'unicorn/prefer-simple-condition-first': 'off', // Needs a short-circuit behavior audit + 'unicorn/prefer-then-catch': 'off', // Rejection handlers broaden rejection boundaries + 'unicorn/prefer-unicode-code-point-escapes': 'off', // Questionable benefit 'unicorn/prevent-abbreviations': 'off', + 'unicorn/single-line-block-comment-style': 'off', // Preserve compact JSDoc typedefs }, }, { @@ -89,6 +113,7 @@ module.exports = [ // These imports are either installed by the package test workflow or // generated during its build; root lint cannot resolve them. files: [ + 'packages/libdatadog/benchmark/agentless-pipeline.js', 'packages/libdatadog/lib/wasm.js', 'packages/libdatadog/remote-config.js', 'packages/libdatadog/test/bundlers.test.js', diff --git a/global.js b/global.js deleted file mode 100644 index 98b66d6f..00000000 --- a/global.js +++ /dev/null @@ -1 +0,0 @@ -globalThis.__dd_collector = require('.') diff --git a/package.json b/package.json index 1445cccc..9d55bb81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/libdatadog-extras", - "version": "0.20.0", + "version": "0.21.0", "description": "Optional native and WASM libdatadog functionality for Node.js", "main": "index.js", "scripts": { @@ -33,10 +33,10 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@stylistic/eslint-plugin": "^5.9.0", - "eslint": "^10.6.0", + "eslint": "^10.9.1", "eslint-plugin-import-x": "^4.17.1", - "eslint-plugin-n": "^17.24.0", - "eslint-plugin-unicorn": "^63.0.0", - "globals": "^17.7.0" + "eslint-plugin-n": "^18.2.2", + "eslint-plugin-unicorn": "^74.0.0", + "globals": "^17.11.0" } } diff --git a/packages/libdatadog/README.md b/packages/libdatadog/README.md index 3ba86705..417eb6eb 100644 --- a/packages/libdatadog/README.md +++ b/packages/libdatadog/README.md @@ -17,6 +17,9 @@ The package accepts Datadog v0.4 MessagePack payloads and exports them to an agentless intake. `sendV04()` reports completion through a callback and sends delivery failures to the supplied logger. It does not return a promise. +`createAgentlessExporter(options, { agent })` accepts an optional borrowed +Node.js HTTP agent. The caller owns the agent and its lifetime. + The package uses a wasm-bindgen backend with the WebAssembly bytes embedded in JavaScript. The canonical inlined output is published as the regular `@datadog/libdatadog-wasm` dependency from the `wasm` workspace. The WASM diff --git a/packages/libdatadog/benchmark/agentless-pipeline.js b/packages/libdatadog/benchmark/agentless-pipeline.js new file mode 100644 index 00000000..ddfc39fa --- /dev/null +++ b/packages/libdatadog/benchmark/agentless-pipeline.js @@ -0,0 +1,355 @@ +'use strict' + +const assert = require('node:assert/strict') + +const { encode } = require('@msgpack/msgpack') +const binding = require('@datadog/libdatadog-wasm') + +const emptyBody = Buffer.alloc(0) +const zstdMagic = Buffer.from([0x28, 0xB5, 0x2F, 0xFD]) +const richMetaStruct = encode({ attempt: 1, feature: 'checkout' }) +const selectedWorkload = process.argv[2] + +/** @typedef {'http' | 'mixed-repeated-sql' | 'mixed-unique-sql' | 'rich'} WorkloadShape */ +/** + * @typedef {object} WorkloadOptions + * @property {string} name + * @property {number} traceCount + * @property {number} spansPerTrace + * @property {WorkloadShape} shape + * @property {number} iterations + */ +/** + * @typedef {object} Workload + * @property {string} name + * @property {number} traceCount + * @property {number} spansPerTrace + * @property {number} iterations + * @property {Uint8Array} payload + */ +/** @typedef {{ name: string, value: string }} Header */ +/** @typedef {{ method: string, url: string, headers: Header[], body: Uint8Array }} RequestPlan */ +/** @typedef {{ status: number, body: Uint8Array }} BindingResponse */ +/** @typedef {{ elapsedNanoseconds: number, outputBytes: number, requests: number, plan?: RequestPlan }} Sample */ + +const exporterOptions = { + endpoint: 'https://example.test/v1/input', + apiKey: 'test-api-key', + hostname: 'benchmark-host', + env: 'benchmark', + service: 'web', + version: '1.0.0', + runtimeId: 'benchmark-runtime', + containerId: 'benchmark-container', + tracerVersion: '0.1.0', + languageVersion: process.version, + languageInterpreter: 'v8', +} + +const workloadOptions = [ + { name: 'tiny', traceCount: 1, spansPerTrace: 1, shape: 'http', iterations: 5000 }, + { name: 'common-http', traceCount: 100, spansPerTrace: 3, shape: 'http', iterations: 300 }, + { + name: 'mixed-repeated-sql', + traceCount: 100, + spansPerTrace: 3, + shape: 'mixed-repeated-sql', + iterations: 200, + }, + { + name: 'mixed-unique-sql', + traceCount: 100, + spansPerTrace: 3, + shape: 'mixed-unique-sql', + iterations: 200, + }, + { name: 'rich', traceCount: 25, spansPerTrace: 8, shape: 'rich', iterations: 200 }, + { name: 'large', traceCount: 1000, spansPerTrace: 5, shape: 'mixed-repeated-sql', iterations: 15 }, +] + +/** @param {WorkloadOptions} options */ +function createWorkload (options) { + const traces = Array.from({ length: options.traceCount }) + for (let traceIndex = 0; traceIndex < traces.length; traceIndex++) { + const spans = Array.from({ length: options.spansPerTrace }) + for (let spanIndex = 0; spanIndex < spans.length; spanIndex++) { + spans[spanIndex] = createSpan(traceIndex, spanIndex, options.spansPerTrace, options.shape) + } + traces[traceIndex] = spans + } + + return { + name: options.name, + traceCount: options.traceCount, + spansPerTrace: options.spansPerTrace, + iterations: options.iterations, + payload: encode(traces, { useBigInt64: true }), + } +} + +/** + * @param {number} traceIndex + * @param {number} spanIndex + * @param {number} spansPerTrace + * @param {WorkloadShape} shape + */ +function createSpan (traceIndex, spanIndex, spansPerTrace, shape) { + const isDatabaseSpan = shape !== 'http' && spanIndex !== 0 + const queryId = shape === 'mixed-unique-sql' + ? traceIndex * spansPerTrace + spanIndex + : 7 + const name = isDatabaseSpan ? 'postgres.query' : 'http.request' + const resource = isDatabaseSpan + ? `SELECT id, email FROM users WHERE tenant_id = 42 AND id = ${queryId}` + : `GET /users/${traceIndex % 100}` + const start = 1_800_000_000_000_000_000n + BigInt(traceIndex * 1_000_000 + spanIndex * 1000) + const meta = { + 'component': isDatabaseSpan ? 'pg' : 'http', + 'env': 'benchmark', + 'span.kind': spanIndex === 0 ? 'server' : 'client', + 'version': '1.0.0', + } + if (isDatabaseSpan) { + meta['db.system'] = 'postgresql' + meta['db.user'] = 'benchmark-user' + } else { + meta['http.method'] = 'GET' + meta['http.status_code'] = '200' + meta['http.url'] = `https://example.test/users/${traceIndex}?token=secret` + } + + const span = { + service: isDatabaseSpan ? 'postgres' : 'web', + name, + resource, + trace_id: BigInt(traceIndex + 1), + span_id: BigInt(traceIndex * 16 + spanIndex + 1), + parent_id: spanIndex === 0 ? 0n : BigInt(traceIndex * 16 + spanIndex), + start, + duration: 100_000 + spanIndex * 1000, + error: shape === 'rich' && spanIndex % 7 === 0 ? 1 : 0, + meta, + metrics: { + '_dd.measured': 1, + '_sampling_priority_v1': 1, + }, + type: isDatabaseSpan ? 'sql' : 'web', + } + + if (shape === 'rich') { + for (let tagIndex = 0; tagIndex < 8; tagIndex++) { + meta[`benchmark.tag.${tagIndex}`] = `value-${traceIndex}-${spanIndex}-${tagIndex}` + } + span.meta_struct = { 'benchmark.context': richMetaStruct } + if (spanIndex % 4 === 0) { + span.span_events = [{ + name: 'exception', + time_unix_nano: start + 500n, + attributes: { + 'exception.count': { type: 2, int_value: 1n }, + 'exception.escaped': { type: 1, bool_value: false }, + 'exception.message': { type: 0, string_value: 'request timed out' }, + }, + }] + span.span_links = [{ + trace_id: BigInt(traceIndex + 10_001), + trace_id_high: 0x12_34n, + span_id: BigInt(spanIndex + 20_001), + attributes: { 'link.name': 'scheduled_by' }, + flags: 1, + tracestate: 'dd=s:1', + }] + } + } + + return span +} + +/** + * @param {Uint8Array} payload + * @param {number} iterations + * @param {boolean} capturePlan + * @returns {Promise} + */ +function runIterations (payload, iterations, capturePlan) { + let rejectRun + let resolveRun + let requests = 0 + let outputBytes = 0 + let plan + /** + * @param {(sample: Sample) => void} resolve + * @param {(error: Error) => void} reject + */ + const completed = new Promise((resolve, reject) => { + resolveRun = resolve + rejectRun = reject + }) + + /** + * @param {RequestPlan} requestPlan + * @param {(error: undefined, response: BindingResponse) => void} done + */ + const request = (requestPlan, done) => { + requests++ + outputBytes = requestPlan.body.byteLength + if (capturePlan && plan === undefined) { + plan = { + ...requestPlan, + body: Buffer.from(requestPlan.body), + } + } + done(undefined, { status: 202, body: emptyBody }) + } + + let operations = 0 + + /** @param {unknown} error */ + const complete = (error) => { + if (error !== undefined) { + exporter.free() + rejectRun(new Error(String(error))) + return + } + + operations++ + if (operations < iterations) { + exporter.sendV04(payload, complete) + return + } + + const elapsedNanoseconds = Number(process.hrtime.bigint() - start) + exporter.free() + resolveRun({ elapsedNanoseconds, outputBytes, requests, plan }) + } + + const exporter = new binding.AgentlessExporter( + exporterOptions, + request, + cancelRequest, + completeSleep, + cancelSleep, + ) + const start = process.hrtime.bigint() + exporter.sendV04(payload, complete) + return completed +} + +function cancelRequest () {} + +/** + * @param {number} id + * @param {number} milliseconds + * @param {() => void} done + */ +function completeSleep (id, milliseconds, done) { + assert(Number.isInteger(id)) + assert(Number.isInteger(milliseconds)) + done() +} + +function cancelSleep () {} + +/** @param {number} left @param {number} right */ +function compareNumbers (left, right) { + return left - right +} + +/** @param {number[]} samples */ +function trimmedMean (samples) { + const sorted = [...samples] + sorted.sort(compareNumbers) + let sum = 0 + for (let index = 1; index < sorted.length - 1; index++) { + sum += sorted[index] + } + return sum / (sorted.length - 2) +} + +/** @param {Workload} workload */ +async function validateWorkload (workload) { + const sample = await runIterations(workload.payload, 1, true) + const { plan } = sample + assert(plan) + assert.strictEqual(sample.requests, 1) + assert.strictEqual(plan.method, 'POST') + assert.strictEqual(plan.url, exporterOptions.endpoint) + assert.strictEqual(findHeader(plan.headers, 'content-encoding'), 'zstd') + assert.strictEqual(findHeader(plan.headers, 'content-type'), 'application/json') + assert.strictEqual(findHeader(plan.headers, 'dd-api-key'), exporterOptions.apiKey) + assert.deepStrictEqual(plan.body.subarray(0, zstdMagic.length), zstdMagic) +} + +/** @param {Header[]} headers @param {string} name */ +function findHeader (headers, name) { + for (const header of headers) { + if (header.name === name) return header.value + } +} + +/** @param {Workload} workload */ +async function benchmarkWorkload (workload) { + await validateWorkload(workload) + const warmupBatchIterations = Math.max(workload.iterations, 50) + let warmupElapsedNanoseconds = 0 + let warmupIterations = 0 + do { + const sample = await runIterations(workload.payload, warmupBatchIterations, false) + warmupElapsedNanoseconds += sample.elapsedNanoseconds + warmupIterations += warmupBatchIterations + } while (warmupElapsedNanoseconds < 1e9) + + const samples = Array.from({ length: 7 }) + let outputBytes = 0 + for (let trial = 0; trial < samples.length; trial++) { + const sample = await runIterations(workload.payload, workload.iterations, false) + assert.strictEqual(sample.requests, workload.iterations) + samples[trial] = sample.elapsedNanoseconds / workload.iterations + outputBytes = sample.outputBytes + } + + const mean = trimmedMean(samples) + return { + name: workload.name, + traceCount: workload.traceCount, + spansPerTrace: workload.spansPerTrace, + inputBytes: workload.payload.byteLength, + outputBytes, + warmupIterations, + iterationsPerTrial: workload.iterations, + samplesNanosecondsPerOperation: samples, + trimmedMeanNanosecondsPerOperation: mean, + operationsPerSecond: 1e9 / mean, + } +} + +function selectedWorkloads () { + const workloads = [] + for (const options of workloadOptions) { + if (selectedWorkload === undefined || selectedWorkload === options.name) { + workloads.push(createWorkload(options)) + } + } + if (workloads.length === 0) { + throw new Error(`unknown workload: ${selectedWorkload}`) + } + return workloads +} + +async function main () { + const results = [] + for (const workload of selectedWorkloads()) { + results.push(await benchmarkWorkload(workload)) + } + console.log(JSON.stringify({ + benchmark: 'agentless-pipeline', + node: process.version, + v8: process.versions.v8, + trials: 7, + results, + })) +} + +// CommonJS does not support top-level await. +// eslint-disable-next-line unicorn/prefer-top-level-await +main() diff --git a/packages/libdatadog/index.d.ts b/packages/libdatadog/index.d.ts index 37b58dee..39f52f7a 100644 --- a/packages/libdatadog/index.d.ts +++ b/packages/libdatadog/index.d.ts @@ -80,6 +80,15 @@ export interface AgentlessExporterOptions { obfuscation?: ObfuscationConfig } +interface AgentlessTransportAgent { + addRequest(request: object, options: object): void +} + +export interface AgentlessTransportOptions { + /** Borrowed Node.js HTTP agent. The exporter does not destroy it. */ + agent?: AgentlessTransportAgent +} + export interface AgentlessExporter { sendV04(payload: Uint8Array, done: () => void, log: AgentlessLogger): void close(): void @@ -89,7 +98,10 @@ export interface AgentlessLogger { error(message: string, ...args: unknown[]): void } -export function createAgentlessExporter(options: AgentlessExporterOptions): AgentlessExporter +export function createAgentlessExporter( + options: AgentlessExporterOptions, + transportOptions?: AgentlessTransportOptions +): AgentlessExporter export function backend(): 'wasm' export function zstd_compress(data: Uint8Array, level: number): Uint8Array diff --git a/packages/libdatadog/lib/agentless-transport.js b/packages/libdatadog/lib/agentless-transport.js index 79e15423..58140dfb 100644 --- a/packages/libdatadog/lib/agentless-transport.js +++ b/packages/libdatadog/lib/agentless-transport.js @@ -1,73 +1,105 @@ 'use strict' -function createHostTransport () { +/** @typedef {{ name: string, value: string }} Header */ +/** @typedef {{ id: number, url: string, method: string, headers: Header[], body: Uint8Array }} RequestPlan */ +/** @typedef {{ status: number | undefined, body: Buffer }} Response */ +/** @typedef {(error?: Error, response?: Response) => void} RequestCallback */ +/** @typedef {import('../index').AgentlessTransportOptions} AgentlessTransportOptions */ + +const maxActiveBufferSize = 16 * 1024 * 1024 +const discardedResponse = { status: 200, body: Buffer.alloc(0) } + +let activeBufferSize = 0 + +/** @param {AgentlessTransportOptions} [options] */ +function createHostTransport ({ agent } = {}) { + const requestAgent = agent ?? false const requests = new Map() const timers = new Map() - function request ({ id, url, method, headers: headerList, body }) { + /** + * @param {RequestPlan} plan + * @param {RequestCallback} done + */ + function request ({ id, url, method, headers: headerList, body }, done) { + const bodySize = body.byteLength + if (activeBufferSize + bodySize > maxActiveBufferSize) { + done(undefined, discardedResponse) + return + } + const target = new URL(url) const client = target.protocol === 'https:' ? require('node:https') : require('node:http') const headers = Object.fromEntries(headerList.map(({ name, value }) => [name, value])) + if (requestAgent === false) headers.connection = 'close' - return new Promise((resolve, reject) => { - let settled = false - const finish = (callback, value) => { - if (settled) return - settled = true - requests.delete(id) - callback(value) - } - const outgoing = client.request(target, { - agent: false, - headers: { ...headers, connection: 'close' }, - method, - }, (response) => { - const chunks = [] - response.on('data', chunk => chunks.push(chunk)) - response.once('aborted', () => finish(reject, new Error('response aborted'))) - response.once('error', error => finish(reject, error)) - response.on('end', () => finish(resolve, { - status: response.statusCode, - body: Buffer.concat(chunks), - })) - }) + let settled = false + /** + * @param {Error | undefined} error + * @param {Response} [response] + * @param {boolean} [notify=true] + */ + const finish = (error, response, notify = true) => { + if (settled) return false + settled = true + requests.delete(id) + activeBufferSize -= bodySize + if (notify) done(error, response) + return true + } + const outgoing = client.request(target, { + agent: requestAgent, + headers, + method, + }, (response) => { + const chunks = [] + response.on('data', chunk => chunks.push(chunk)) + response.once('aborted', () => finish(new Error('response aborted'))) + response.once('error', finish) + response.once('end', () => finish(undefined, { + status: response.statusCode, + body: Buffer.concat(chunks), + })) + }) + activeBufferSize += bodySize - requests.set(id, { - cancel: () => { - const error = new Error('agentless request was cancelled') - finish(reject, error) - outgoing.destroy(error) - }, - }) - outgoing.once('error', error => finish(reject, error)) - outgoing.end(body) + requests.set(id, { + cancel: () => { + if (finish(undefined, undefined, false)) outgoing.destroy() + }, }) + outgoing.once('error', finish) + outgoing.end(body) } + /** @param {number} id */ function cancelRequest (id) { requests.get(id)?.cancel() } // TODO(libdd-capabilities): Make host-backed capability futures cancel their - // underlying operation when dropped. Then sleep can return a cancellable - // operation directly, removing timer IDs, the timers map, and cancelSleep. - function sleep (id, milliseconds) { - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { + // underlying operation when dropped. Then the transport can remove timer IDs, + // the timers map, and cancelSleep. + /** + * @param {number} id + * @param {number} milliseconds + * @param {() => void} done + */ + function sleep (id, milliseconds, done) { + const timeout = setTimeout(() => { + timers.delete(id) + done() + }, milliseconds) + timeout.unref?.() + timers.set(id, { + cancel: () => { + clearTimeout(timeout) timers.delete(id) - resolve() - }, milliseconds) - timeout.unref?.() - timers.set(id, { - cancel: () => { - clearTimeout(timeout) - timers.delete(id) - reject(new Error('agentless timer was cancelled')) - }, - }) + }, }) } + /** @param {number} id */ function cancelSleep (id) { timers.get(id)?.cancel() } diff --git a/packages/libdatadog/lib/agentless.js b/packages/libdatadog/lib/agentless.js index 5ea5a7f5..7e0acebb 100644 --- a/packages/libdatadog/lib/agentless.js +++ b/packages/libdatadog/lib/agentless.js @@ -5,6 +5,7 @@ const { randomUUID } = require('node:crypto') const { createHostTransport } = require('./agentless-transport') /** @typedef {import('../index').AgentlessExporterOptions} AgentlessExporterOptions */ +/** @typedef {import('../index').AgentlessTransportOptions} AgentlessTransportOptions */ /** @typedef {import('../index').AgentlessLogger} AgentlessLogger */ /** @typedef {typeof import('@datadog/libdatadog-wasm')} AgentlessBinding */ @@ -17,13 +18,14 @@ class AgentlessExporter { /** * @param {AgentlessBinding} binding * @param {AgentlessExporterOptions} options + * @param {AgentlessTransportOptions} [transportOptions] */ - constructor (binding, options) { + constructor (binding, options, transportOptions) { const { runtimeId } = options const bindingOptions = runtimeId === undefined || runtimeId === null ? { ...options, runtimeId: randomUUID() } : options - const transport = createHostTransport() + const transport = createHostTransport(transportOptions) this.#binding = new binding.AgentlessExporter( bindingOptions, transport.request, @@ -45,25 +47,23 @@ class AgentlessExporter { return } - let operation + /** @param {unknown} error */ + const complete = (error) => { + if (error !== undefined) { + const message = errorMessage(error) + if (!this.#closed || message !== canceledError) { + log.error('Failed to send data-pipeline export: %s', message) + } + } + done() + } + try { - operation = this.#binding.sendV04(payload) + this.#binding.sendV04(payload, complete) } catch (error) { log.error('Failed to send data-pipeline export: %s', errorMessage(error)) done() - return } - - operation.then( - done, - (error) => { - const message = errorMessage(error) - if (!this.#closed || message !== canceledError) { - log.error('Failed to send data-pipeline export: %s', message) - } - done() - }, - ) } close () { @@ -83,9 +83,10 @@ function errorMessage (error) { /** * @param {AgentlessBinding} binding * @param {AgentlessExporterOptions} options + * @param {AgentlessTransportOptions} [transportOptions] */ -function createAgentlessExporter (binding, options) { - return new AgentlessExporter(binding, options) +function createAgentlessExporter (binding, options, transportOptions) { + return new AgentlessExporter(binding, options, transportOptions) } module.exports = { createAgentlessExporter } diff --git a/packages/libdatadog/lib/wasm.js b/packages/libdatadog/lib/wasm.js index 3bb9c3d5..e2eefa0c 100644 --- a/packages/libdatadog/lib/wasm.js +++ b/packages/libdatadog/lib/wasm.js @@ -9,7 +9,10 @@ module.exports = { zstd_compress: binding.zstd_compress, } -/** @param {import('../index').AgentlessExporterOptions} options */ -function createAgentlessExporter (options) { - return require('./agentless').createAgentlessExporter(binding, options) +/** + * @param {import('../index').AgentlessExporterOptions} options + * @param {import('../index').AgentlessTransportOptions} [transportOptions] + */ +function createAgentlessExporter (options, transportOptions) { + return require('./agentless').createAgentlessExporter(binding, options, transportOptions) } diff --git a/packages/libdatadog/package-lock.json b/packages/libdatadog/package-lock.json index 690af2b0..8d0d5f42 100644 --- a/packages/libdatadog/package-lock.json +++ b/packages/libdatadog/package-lock.json @@ -1,18 +1,18 @@ { "name": "@datadog/libdatadog", - "version": "0.20.0", + "version": "0.21.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@datadog/libdatadog", - "version": "0.20.0", + "version": "0.21.0", "license": "Apache-2.0", "workspaces": [ "wasm" ], "dependencies": { - "@datadog/libdatadog-wasm": "0.20.0" + "@datadog/libdatadog-wasm": "0.21.0" }, "devDependencies": { "@msgpack/msgpack": "^3.1.3", @@ -1901,7 +1901,7 @@ }, "wasm": { "name": "@datadog/libdatadog-wasm", - "version": "0.20.0", + "version": "0.21.0", "license": "Apache-2.0", "engines": { "node": ">=18" diff --git a/packages/libdatadog/package.json b/packages/libdatadog/package.json index 83bbdde3..b0a4c163 100644 --- a/packages/libdatadog/package.json +++ b/packages/libdatadog/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/libdatadog", - "version": "0.20.0", + "version": "0.21.0", "description": "WASM Node.js bindings for libdatadog", "license": "Apache-2.0", "repository": { @@ -53,6 +53,7 @@ "node": ">=18" }, "scripts": { + "benchmark:agentless": "npm run build:wasm:binary && npm run inline:wasm && node benchmark/agentless-pipeline.js", "build:wasm": "npm run build:wasm:binary && npm run build:remote-config:binary && npm run inline:wasm && npm run inline:remote-config", "build:wasm:binary": "node ../../scripts/build-wasm.js ../../crates/libdatadog-wasm wasm/dist", "build:remote-config:binary": "node ../../scripts/build-wasm.js ../../crates/remote_config wasm/dist/remote-config", @@ -66,7 +67,7 @@ "test:types": "tsc --noEmit --strict --target ES2022 --module Node16 --moduleResolution Node16 test/types.test.ts" }, "dependencies": { - "@datadog/libdatadog-wasm": "0.20.0" + "@datadog/libdatadog-wasm": "0.21.0" }, "devDependencies": { "@msgpack/msgpack": "^3.1.3", diff --git a/packages/libdatadog/scripts/inline-wasm.js b/packages/libdatadog/scripts/inline-wasm.js index fe164f11..ea1634b8 100644 --- a/packages/libdatadog/scripts/inline-wasm.js +++ b/packages/libdatadog/scripts/inline-wasm.js @@ -33,7 +33,7 @@ fs.writeFileSync( gluePath, glue.replace( loader, - `const wasmBytes = require('node:zlib').brotliDecompressSync(Buffer.from('${encodedWasm}', 'base64'));`, + () => `const wasmBytes = require('node:zlib').brotliDecompressSync(Buffer.from('${encodedWasm}', 'base64'));`, ), ) fs.rmSync(wasmPath) diff --git a/packages/libdatadog/test/exporter.test.js b/packages/libdatadog/test/exporter.test.js index bcb22c74..9bd9d513 100644 --- a/packages/libdatadog/test/exporter.test.js +++ b/packages/libdatadog/test/exporter.test.js @@ -9,11 +9,48 @@ const { zstdDecompressSync } = require('node:zlib') const { encode } = require('@msgpack/msgpack') +const { createHostTransport } = require('../lib/agentless-transport') + const zstdMagic = Buffer.from([0x28, 0xB5, 0x2F, 0xFD]) const packageRoot = path.join(__dirname, '..') const wasmArtifact = path.join(packageRoot, 'wasm', 'dist', 'libdatadog_wasm.js') +/** @typedef {(error?: unknown) => void} BindingDone */ + +class TrackingAgent extends http.Agent { + connections = 0 + requests = 0 + destroyed = false + + constructor () { + super({ keepAlive: true }) + } + + /** + * @param {import('node:http').ClientRequestArgs} options + * @param {(error: Error | null, stream: import('node:stream').Duplex) => void} [callback] + */ + createConnection (options, callback) { + this.connections++ + return super.createConnection(options, callback) + } + + /** + * @param {import('node:http').ClientRequest} request + * @param {import('node:http').RequestOptions} options + */ + addRequest (request, options) { + this.requests++ + super.addRequest(request, options) + } + + destroy () { + this.destroyed = true + super.destroy() + } +} + test('package entry points defer unused agentless modules', { skip: !fs.existsSync(wasmArtifact), }, () => { @@ -28,8 +65,12 @@ test('package entry points defer unused agentless modules', { test('agentless exporter reports completion through its callback', async () => { class BindingExporter { - sendV04 () { - return Promise.resolve() + /** + * @param {Uint8Array} payload + * @param {BindingDone} done + */ + sendV04 (payload, done) { + done() } cancelAll () {} @@ -53,8 +94,12 @@ test('agentless exporter reports completion through its callback', async () => { test('agentless exporter logs asynchronous failures before reporting completion', async () => { class BindingExporter { - sendV04 () { - return Promise.reject('intake unavailable') + /** + * @param {Uint8Array} payload + * @param {BindingDone} done + */ + sendV04 (payload, done) { + queueMicrotask(() => done('intake unavailable')) } cancelAll () {} @@ -105,13 +150,15 @@ test('agentless exporter logs synchronous failures before reporting completion', }) test('agentless exporter logs failures settled before close', async () => { - let rejectSend + let completeSend class BindingExporter { - sendV04 () { - return new Promise((resolve, reject) => { - rejectSend = reject - }) + /** + * @param {Uint8Array} payload + * @param {BindingDone} done + */ + sendV04 (payload, done) { + completeSend = done } cancelAll () {} @@ -127,7 +174,7 @@ test('agentless exporter logs failures settled before close', async () => { }, log) }) - rejectSend('intake unavailable') + completeSend('intake unavailable') exporter.close() await send @@ -145,7 +192,6 @@ test('agentless exporter reports sends after close without calling the binding', class BindingExporter { sendV04 () { sends++ - return Promise.resolve() } cancelAll () { @@ -175,6 +221,21 @@ test('package entry point compresses agentless v0.4 exports with Zstandard', { await assertExport(require('..')) }) +test('package entry point uses a borrowed transport agent', { + skip: !fs.existsSync(wasmArtifact), +}, async () => { + const agent = new TrackingAgent() + + try { + await assertExport(require('..'), { agent }, 2) + assert.strictEqual(agent.requests, 2) + assert.strictEqual(agent.connections, 1) + assert.strictEqual(agent.destroyed, false) + } finally { + agent.destroy() + } +}) + test('inline-WASM backend validates optional values', { skip: !fs.existsSync(wasmArtifact), }, async () => { @@ -230,6 +291,45 @@ test('agentless exporter retries in Rust until the third attempt succeeds', { } }) +test('agentless exporter drops over-budget requests without retrying', { + skip: !fs.existsSync(wasmArtifact), +}, async () => { + const pipeline = require('../wasm') + const transport = createHostTransport() + let requests = 0 + let resolveFirstRequest + const firstRequestReceived = new Promise((resolve) => { + resolveFirstRequest = resolve + }) + const server = http.createServer((incoming) => { + requests++ + incoming.resume() + resolveFirstRequest() + }) + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + transport.request({ + id: 1, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: Buffer.alloc(16 * 1024 * 1024), + }, assert.fail) + const exporter = createExporter(pipeline, server) + try { + await firstRequestReceived + const log = testLog() + await sendExport(exporter, log) + assert.deepStrictEqual(log.errors, []) + assert.strictEqual(requests, 1) + } finally { + exporter.close() + transport.cancelRequest(1) + await new Promise(resolve => server.close(resolve)) + } +}) + test('agentless exporter applies Rust timeouts and retry policy', { skip: !fs.existsSync(wasmArtifact), }, async () => { @@ -256,6 +356,32 @@ test('agentless exporter applies Rust timeouts and retry policy', { } }) +test('agentless exporter close cancels a send started in the same turn', { + skip: !fs.existsSync(wasmArtifact), +}, async () => { + const pipeline = require('../wasm') + let requests = 0 + const server = http.createServer((incoming, response) => { + requests++ + incoming.resume() + incoming.once('end', () => response.end()) + }) + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const exporter = createExporter(pipeline, server) + try { + const log = testLog() + const send = sendExport(exporter, log) + exporter.close() + await send + assert.strictEqual(requests, 0) + assert.deepStrictEqual(log.errors, []) + } finally { + exporter.close() + await new Promise(resolve => server.close(resolve)) + } +}) + test('agentless exporter close cancels an active HTTP request', { skip: !fs.existsSync(wasmArtifact), }, async () => { @@ -322,8 +448,10 @@ test('agentless exporter close cancels retry backoff', { /** * @param {typeof import('..')} pipeline + * @param {import('../index').AgentlessTransportOptions} [transportOptions] + * @param {number} [count] */ -async function assertExport (pipeline) { +async function assertExport (pipeline, transportOptions, count = 1) { const received = await withIntake(async (endpoint) => { const exporter = pipeline.createAgentlessExporter({ endpoint, @@ -334,10 +462,12 @@ async function assertExport (pipeline) { runtimeId: 'runtime-id', service: 'service', containerId: 'container-id', - }) + }, transportOptions) try { - await sendExport(exporter) + for (let i = 0; i < count; i++) { + await sendExport(exporter) + } } finally { exporter.close() } @@ -387,7 +517,7 @@ function createExporter (pipeline, server, options = {}) { function exporterOptions () { return { - endpoint: 'http://example.test/api/v2/spans', + endpoint: 'https://example.test/api/v2/spans', apiKey: 'test-api-key', tracerVersion: '0.1.0', languageVersion: process.version, @@ -397,7 +527,7 @@ function exporterOptions () { /** * @typedef {object} TestBindingExporter - * @property {(payload: Uint8Array) => Promise} sendV04 + * @property {(payload: Uint8Array, done: BindingDone) => void} sendV04 * @property {() => void} cancelAll */ diff --git a/packages/libdatadog/test/package.test.js b/packages/libdatadog/test/package.test.js index a4579af2..ebb9f24d 100644 --- a/packages/libdatadog/test/package.test.js +++ b/packages/libdatadog/test/package.test.js @@ -11,7 +11,7 @@ const packageRoot = path.join(__dirname, '..') const repositoryRoot = path.join(packageRoot, '..', '..') test('publishes the universal libdatadog package', () => { - const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'))) + const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')) assert.strictEqual(packageJson.name, '@datadog/libdatadog') assert.strictEqual(packageJson.exports['./wasm'].require, './wasm.js') @@ -20,6 +20,7 @@ test('publishes the universal libdatadog package', () => { const wasmPackageJson = JSON.parse(fs.readFileSync( path.join(packageRoot, 'wasm', 'package.json'), + 'utf8', )) assert.strictEqual( wasmPackageJson.exports['./remote-config'].require, @@ -28,11 +29,12 @@ test('publishes the universal libdatadog package', () => { }) test('uses the libdatadog release version', () => { - const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'))) + const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')) const wasmPackageJson = JSON.parse(fs.readFileSync( path.join(packageRoot, 'wasm', 'package.json'), + 'utf8', )) - const repositoryPackageJson = JSON.parse(fs.readFileSync(path.join(repositoryRoot, 'package.json'))) + const repositoryPackageJson = JSON.parse(fs.readFileSync(path.join(repositoryRoot, 'package.json'), 'utf8')) assert.strictEqual(packageJson.version, repositoryPackageJson.version) assert.strictEqual(wasmPackageJson.version, repositoryPackageJson.version) @@ -55,7 +57,7 @@ test('carries the repository metadata npm provenance verifies against', () => { path.join(packageRoot, 'package.json'), path.join(packageRoot, 'wasm', 'package.json'), ]) { - const manifest = JSON.parse(fs.readFileSync(manifestPath)) + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) assert.strictEqual(manifest.repository?.url, expectedUrl, `${manifest.name} repository.url`) } diff --git a/packages/libdatadog/test/remote-config.test.js b/packages/libdatadog/test/remote-config.test.js index 3ff4ac0b..3b89f886 100644 --- a/packages/libdatadog/test/remote-config.test.js +++ b/packages/libdatadog/test/remote-config.test.js @@ -115,6 +115,42 @@ test('exports agentless remote config from the dedicated entry point', async () } }) +test('rejects an aborted agentless remote config response', async () => { + const originalRequest = https.request + let requestCount = 0 + + /** + * @param {import('node:https').RequestOptions} requestOptions + * @param {(response: import('node:http').IncomingMessage) => void} onResponse + */ + function request (requestOptions, onResponse) { + assert.strictEqual(typeof requestOptions.method, 'string') + requestCount++ + const outgoing = new EventEmitter() + outgoing.write = () => {} + outgoing.end = () => { + queueMicrotask(() => { + const response = new EventEmitter() + response.statusCode = 200 + response.rawHeaders = [] + onResponse(response) + response.emit('aborted') + }) + } + return outgoing + } + + https.request = request + try { + const fetcher = new RemoteConfigFetcher(fetcherOptions()) + + await assert.rejects(fetcher.fetchChanges(), /response aborted/) + assert(requestCount > 0) + } finally { + https.request = originalRequest + } +}) + /** @param {() => void} callback */ function runWithoutStorage (callback) { callback() diff --git a/packages/libdatadog/test/transport.test.js b/packages/libdatadog/test/transport.test.js index 6e55df6f..803d6216 100644 --- a/packages/libdatadog/test/transport.test.js +++ b/packages/libdatadog/test/transport.test.js @@ -6,6 +6,26 @@ const { test } = require('node:test') const { createHostTransport } = require('../lib/agentless-transport') +/** @typedef {ReturnType} HostTransport */ +/** @typedef {Parameters[0]} RequestPlan */ + +/** + * @param {HostTransport} transport + * @param {RequestPlan} plan + */ +function sendRequest (transport, plan) { + return new Promise((resolve, reject) => { + const result = transport.request(plan, (error, response) => { + if (error) { + reject(error) + } else { + resolve(response) + } + }) + assert.strictEqual(result, undefined) + }) +} + test('host transport rejects when a response is aborted', async () => { const transport = createHostTransport() let resolveResponseClosed @@ -22,22 +42,15 @@ test('host transport rejects when a response is aborted', async () => { await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) try { const { port } = server.address() - const result = transport.request({ + const result = assert.rejects(sendRequest(transport, { id: 1, url: `http://127.0.0.1:${port}`, method: 'POST', headers: [], body: Buffer.alloc(0), - }).then( - () => ({ status: 'resolved' }), - error => ({ error, status: 'rejected' }), - ) + }), /response aborted|aborted/) - await responseClosed - const outcome = await result - - assert.strictEqual(outcome.status, 'rejected') - assert.match(outcome.error.message, /response aborted|aborted/) + await Promise.all([responseClosed, result]) } finally { await new Promise(resolve => server.close(resolve)) } @@ -45,6 +58,7 @@ test('host transport rejects when a response is aborted', async () => { test('host transport cancels an active request', async () => { const transport = createHostTransport() + let completed = 0 let resolveRequest const received = new Promise((resolve) => { resolveRequest = resolve @@ -57,26 +71,104 @@ test('host transport cancels an active request', async () => { await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) try { const { port } = server.address() - const request = transport.request({ + const result = transport.request({ id: 2, url: `http://127.0.0.1:${port}`, method: 'POST', headers: [], body: Buffer.alloc(0), - }) + }, () => completed++) + assert.strictEqual(result, undefined) await received transport.cancelRequest(2) - await assert.rejects(request, /request was cancelled/) + assert.strictEqual(completed, 0) + } finally { + await new Promise(resolve => server.close(resolve)) + } +}) + +test('host transport bounds active request buffers', async () => { + const transport = createHostTransport() + let requestCount = 0 + let resolveFirstRequest + const firstRequestReceived = new Promise((resolve) => { + resolveFirstRequest = resolve + }) + /** + * @param {import('node:http').IncomingMessage} request + * @param {import('node:http').ServerResponse} response + */ + const server = http.createServer((request, response) => { + requestCount++ + if (requestCount === 1) { + request.resume() + resolveFirstRequest() + return + } + request.once('end', () => response.end()) + request.resume() + }) + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + const { port } = server.address() + const atLimitBody = Buffer.alloc(16 * 1024 * 1024) + const overLimitBody = Buffer.alloc(atLimitBody.length + 1) + const firstRequest = transport.request({ + id: 4, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: atLimitBody, + }, assert.fail) + assert.strictEqual(firstRequest, undefined) + + try { + await firstRequestReceived + const discarded = await sendRequest(transport, { + id: 5, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: Buffer.alloc(1), + }) + assert.strictEqual(discarded.status, 200) + assert.strictEqual(discarded.body.length, 0) + + transport.cancelRequest(4) + + const oversized = await sendRequest(transport, { + id: 6, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: overLimitBody, + }) + assert.strictEqual(oversized.status, 200) + assert.strictEqual(oversized.body.length, 0) + assert.strictEqual(requestCount, 1) + + const nextResponse = await sendRequest(transport, { + id: 7, + url: `http://127.0.0.1:${port}`, + method: 'POST', + headers: [], + body: Buffer.alloc(1), + }) + assert.strictEqual(nextResponse.status, 200) + assert.strictEqual(requestCount, 2) } finally { + transport.cancelRequest(4) await new Promise(resolve => server.close(resolve)) } }) -test('host transport cancels a pending timer', async () => { +test('host transport cancels a pending timer', () => { const transport = createHostTransport() - const sleep = transport.sleep(3, 60_000) + let completed = 0 + const pendingSleep = transport.sleep(3, 60_000, () => completed++) + assert.strictEqual(pendingSleep, undefined) transport.cancelSleep(3) - await assert.rejects(sleep, /timer was cancelled/) + assert.strictEqual(completed, 0) }) diff --git a/packages/libdatadog/test/types.test.ts b/packages/libdatadog/test/types.test.ts index 3775554a..9a08e4eb 100644 --- a/packages/libdatadog/test/types.test.ts +++ b/packages/libdatadog/test/types.test.ts @@ -1,5 +1,6 @@ import { type AgentlessLogger, + type AgentlessTransportOptions, backend, createAgentlessExporter, DDSketch, @@ -20,7 +21,15 @@ const agentlessExporter = createAgentlessExporter({ tracerVersion: '1.2.3', languageVersion: '22.0.0', languageInterpreter: 'v8', +}, { + agent: { + addRequest () {}, + }, }) +const invalidTransportOptions: AgentlessTransportOptions = { + // @ts-expect-error Transport agents must implement addRequest. + agent: {}, +} const logger: AgentlessLogger = { error () {}, } diff --git a/packages/libdatadog/wasm/package.json b/packages/libdatadog/wasm/package.json index 70f49d52..59a319a5 100644 --- a/packages/libdatadog/wasm/package.json +++ b/packages/libdatadog/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@datadog/libdatadog-wasm", - "version": "0.20.0", + "version": "0.21.0", "description": "WASM fallback for @datadog/libdatadog", "license": "Apache-2.0", "repository": { diff --git a/scripts/build-wasm.js b/scripts/build-wasm.js index a6717f93..62ef1a02 100644 --- a/scripts/build-wasm.js +++ b/scripts/build-wasm.js @@ -16,6 +16,10 @@ const path = require('node:path') const childProcess = require('node:child_process') const isMacOS = os.platform() === 'darwin' +const wasmRustToolchain = fs.readFileSync( + path.join(__dirname, '..', 'wasm-rust-toolchain'), + 'utf8', +).trim() const libraries = [ 'library_config', 'pipeline', @@ -25,6 +29,20 @@ const libraries = [ const env = { ...process.env, } +const pathKey = Object.keys(env).find(key => key.toUpperCase() === 'PATH') ?? 'PATH' +const rustFlagsKey = Object.keys(env).find(key => key.toUpperCase() === 'RUSTFLAGS') ?? 'RUSTFLAGS' +const rustupToolchainKey = Object.keys(env).find(key => key.toUpperCase() === 'RUSTUP_TOOLCHAIN') ?? 'RUSTUP_TOOLCHAIN' +const cargoPath = childProcess.execFileSync( + 'rustup', + ['which', 'cargo', '--toolchain', wasmRustToolchain], + { encoding: 'utf8' }, +).trim() + +env[pathKey] = `${path.dirname(cargoPath)}${path.delimiter}${env[pathKey]}` +env[rustFlagsKey] = [env[rustFlagsKey], '-Zunstable-options', '-Cpanic=immediate-abort'] + .filter(Boolean) + .join(' ') +env[rustupToolchainKey] = wasmRustToolchain if (isMacOS) { const homebrewDir = env.HOMEBREW_DIR ?? '/opt/homebrew' @@ -42,9 +60,9 @@ if (isMacOS) { process.exit(1) // eslint-disable-line unicorn/no-process-exit } - if (!env.PATH.includes(llvmBinDir)) { + if (!env[pathKey].includes(llvmBinDir)) { // Add LLVM to PATH if not already included - env.PATH = `${llvmBinDir}:${env.PATH}` + env[pathKey] = `${llvmBinDir}${path.delimiter}${env[pathKey]}` } // Force C/C++ code (e.g. zstd-sys) to use Homebrew's clang for wasm32. Otherwise a global @@ -68,7 +86,7 @@ function buildWasm (cratePath, outputDirectory, options = {}) { const args = ['build'] if (profiling) args.push('--profiling') if (skipOptimization) args.push('--no-opt') - args.push('--target', 'nodejs', cratePath, '--out-dir', resolvedOutputDirectory) + args.push('--target', 'nodejs', cratePath, '--out-dir', resolvedOutputDirectory, '--', '-Z', 'build-std=std') childProcess.execFileSync('wasm-pack', args, { env: { ...env, diff --git a/test/crashtracker/app-uncaught-exception-cross-context.js b/test/crashtracker/app-uncaught-exception-cross-context.js new file mode 100644 index 00000000..408040a7 --- /dev/null +++ b/test/crashtracker/app-uncaught-exception-cross-context.js @@ -0,0 +1,28 @@ +'use strict' + +const vm = require('node:vm') + +const libdatadog = require('../..') + +const { initTestCrashtracker } = require('./test-utils') + +const crashtracker = libdatadog.load('crashtracker') + +initTestCrashtracker() +crashtracker.beginProfilerSerializing() + +/** + * @param {unknown} error + * @param {string} origin + */ +function reportUncaughtException (error, origin) { + crashtracker.reportUncaughtExceptionMonitor(error, origin) +} + +function customerVmHandler () { + vm.runInNewContext('throw new TypeError("cross-realm failure")') +} + +process.on('uncaughtExceptionMonitor', reportUncaughtException) + +customerVmHandler() diff --git a/test/crashtracker/app-uncaught-exception-proxy-error.js b/test/crashtracker/app-uncaught-exception-proxy-error.js new file mode 100644 index 00000000..00c4095f --- /dev/null +++ b/test/crashtracker/app-uncaught-exception-proxy-error.js @@ -0,0 +1,35 @@ +'use strict' + +const libdatadog = require('../..') + +const { initTestCrashtracker } = require('./test-utils') + +const crashtracker = libdatadog.load('crashtracker') + +initTestCrashtracker() +crashtracker.beginProfilerSerializing() + +/** + * @param {unknown} error + * @param {string} origin + */ +function reportUncaughtException (error, origin) { + crashtracker.reportUncaughtExceptionMonitor(error, origin) +} + +/** + * @param {TypeError} target + * @param {string | symbol} property + */ +function getErrorProperty (target, property) { + return Reflect.get(target, property, target) +} + +function customerProxyHandler () { + const error = new TypeError('proxied failure') + throw new Proxy(error, { get: getErrorProperty }) +} + +process.on('uncaughtExceptionMonitor', reportUncaughtException) + +customerProxyHandler() diff --git a/test/crashtracker/index.js b/test/crashtracker/index.js index 9c6c2c00..a235a3e4 100644 --- a/test/crashtracker/index.js +++ b/test/crashtracker/index.js @@ -148,6 +148,16 @@ const server = app.listen(async () => { expectedMessage: 'something went wrong', expectedFrame: 'myFaultyFunction', }) + await testUnhandledError('uncaught-exception-cross-context', 'app-uncaught-exception-cross-context', { + expectedType: 'TypeError', + expectedMessage: 'cross-realm failure', + expectedFrame: 'customerVmHandler', + }) + await testUnhandledError('uncaught-exception-proxy-error', 'app-uncaught-exception-proxy-error', { + expectedType: 'TypeError', + expectedMessage: 'proxied failure', + expectedFrame: 'customerProxyHandler', + }) await testUnhandledNonError('uncaught-exception-non-error', 'app-uncaught-exception-non-error', { expectedFallbackType: 'uncaughtException', expectedValue: 'a plain string error', diff --git a/test/crashtracker/package-lock.json b/test/crashtracker/package-lock.json index 92fc554d..7267e4ce 100644 --- a/test/crashtracker/package-lock.json +++ b/test/crashtracker/package-lock.json @@ -6,7 +6,7 @@ "": { "dependencies": { "@datadog/segfaultify": "^0.1.1", - "body-parser": "^1.20.3", + "body-parser": "^2.3.0", "express": "^5.2.1" } }, @@ -32,22 +32,21 @@ "node": ">= 0.6" } }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -57,28 +56,17 @@ "url": "https://opencollective.com/express" } }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/bytes": { @@ -160,12 +148,20 @@ } }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/depd": { @@ -177,16 +173,6 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -235,9 +221,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -304,177 +290,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/express/node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -496,29 +311,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -608,9 +400,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -640,15 +432,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { @@ -682,12 +478,16 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { @@ -703,30 +503,34 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/negotiator": { @@ -815,12 +619,13 @@ } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -839,18 +644,18 @@ } }, "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/router": { @@ -869,29 +674,6 @@ "node": ">= 18" } }, - "node_modules/router/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/router/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -924,54 +706,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/send/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", @@ -998,14 +732,14 @@ "license": "ISC" }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -1017,13 +751,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -1088,16 +822,34 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/unpipe": { diff --git a/test/crashtracker/package.json b/test/crashtracker/package.json index 4a3cd149..e6dfdd45 100644 --- a/test/crashtracker/package.json +++ b/test/crashtracker/package.json @@ -3,7 +3,7 @@ "main": "index.js", "dependencies": { "@datadog/segfaultify": "^0.1.1", - "body-parser": "^1.20.6", + "body-parser": "^2.3.0", "express": "^5.2.1" } } diff --git a/test/crashtracker/yarn.lock b/test/crashtracker/yarn.lock index 04aafa13..127b99eb 100644 --- a/test/crashtracker/yarn.lock +++ b/test/crashtracker/yarn.lock @@ -17,25 +17,7 @@ accepts@^2.0.0: mime-types "^3.0.0" negotiator "^1.0.0" -body-parser@^1.20.6: - version "1.20.6" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76" - integrity sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g== - dependencies: - bytes "~3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "~1.2.0" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - on-finished "~2.4.1" - qs "~6.15.1" - raw-body "~2.5.3" - type-is "~1.6.18" - unpipe "~1.0.0" - -body-parser@^2.2.1: +body-parser@^2.2.1, body-parser@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.3.0.tgz#6d8662f4d8c336028b8ac9aa24251b0ca64ba437" integrity sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw== @@ -76,7 +58,7 @@ content-disposition@^1.0.0: resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.1.0.tgz#f3db789c752d45564cc7e9e1e0b31790d4a38e17" integrity sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g== -content-type@^1.0.5, content-type@~1.0.5: +content-type@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -96,13 +78,6 @@ cookie@^0.7.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@^4.4.0, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" @@ -110,16 +85,11 @@ debug@^4.4.0, debug@^4.4.3: dependencies: ms "^2.1.3" -depd@2.0.0, depd@^2.0.0, depd@~2.0.0: +depd@^2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -destroy@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -286,13 +256,6 @@ iconv-lite@^0.7.2, iconv-lite@~0.7.0: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -iconv-lite@~0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" @@ -313,11 +276,6 @@ math-intrinsics@^1.1.0: resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - media-typer@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" @@ -328,11 +286,6 @@ merge-descriptors@^2.0.0: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - mime-db@^1.54.0: version "1.54.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" @@ -345,18 +298,6 @@ mime-types@^3.0.0, mime-types@^3.0.2: dependencies: mime-db "^1.54.0" -mime-types@~2.1.24: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -377,7 +318,7 @@ object-inspect@^1.13.3, object-inspect@^1.13.4: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== -on-finished@^2.4.1, on-finished@~2.4.1: +on-finished@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -417,14 +358,6 @@ qs@^6.14.0, qs@^6.15.2: es-define-property "^1.0.1" side-channel "^1.1.1" -qs@~6.15.1: - version "6.15.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b" - integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A== - dependencies: - es-define-property "^1.0.1" - side-channel "^1.1.1" - range-parser@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -440,16 +373,6 @@ raw-body@^3.0.2: iconv-lite "~0.7.0" unpipe "~1.0.0" -raw-body@~2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" - integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== - dependencies: - bytes "~3.1.2" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - unpipe "~1.0.0" - router@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" @@ -461,7 +384,7 @@ router@^2.2.0: parseurl "^1.3.3" path-to-regexp "^8.0.0" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -557,14 +480,6 @@ type-is@^2.0.1, type-is@^2.1.0: media-typer "^1.1.0" mime-types "^3.0.0" -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" diff --git a/test/http_transport.js b/test/http_transport.js index 7333d8e8..3a78d592 100644 --- a/test/http_transport.js +++ b/test/http_transport.js @@ -6,18 +6,18 @@ // from a Uint8Array view over `wasm_memory.buffer`, so we hand it a fake memory // object containing a well-formed HTTP/1.1 request head. -const { describe, it, before, after, beforeEach } = require('node:test') -const assert = require('node:assert') +const assert = require('node:assert/strict') +const fs = require('node:fs') const http = require('node:http') const os = require('node:os') const path = require('node:path') -const fs = require('node:fs') +const { PassThrough } = require('node:stream') +const { describe, it, before, after, beforeEach } = require('node:test') const transport = require('../crates/capabilities/src/http_transport') -// Distinctive, multi-byte body so the pooled-buffer slicing in httpRequest -// (the reason for `new Uint8Array(body)` over `body.buffer`) is exercised: -// a small Buffer.concat result lands at a non-zero offset in Node's shared pool. +// Distinctive, multi-byte body so the pooled Buffer bounds in httpRequest are exercised. +// A small Buffer.concat result can start at a non-zero offset in Node's shared pool. const RESPONSE_BODY = '{"rate_by_service":{"service:test,env:":0.5}}' function fakeWasmMemory (headBytes) { @@ -26,6 +26,112 @@ function fakeWasmMemory (headBytes) { return { buffer: buf } } +/** + * @param {'end' | 'error' | 'request-error' | 'write-error'} event + * @param {Error} [error] + */ +async function requestWithResponseEvent (event, error) { + const originalRequest = http.request + const head = Buffer.from('POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n') + + /** + * @param {import('node:http').RequestOptions} requestOptions + * @param {(response: import('node:http').IncomingMessage) => void} onResponse + */ + function request (requestOptions, onResponse) { + assert.strictEqual(requestOptions.method, 'POST') + const outgoing = new PassThrough() + outgoing.write = () => { + if (event === 'write-error') throw error + return true + } + outgoing.end = () => { + queueMicrotask(() => { + if (event === 'request-error') { + outgoing.emit('error', error) + return + } + + const response = new PassThrough() + response.statusCode = 200 + response.rawHeaders = [] + onResponse(response) + response.emit('data', Buffer.from('partial')) + response.emit(event, error) + }) + } + return outgoing + } + + http.request = request + try { + return await transport.httpRequest('localhost', 80, false, '', true, 0, head.length, 0, 0, fakeWasmMemory(head)) + } finally { + http.request = originalRequest + } +} + +describe('http_transport response lifecycle', () => { + it('resolves complete responses', async () => { + const [status, , body] = await requestWithResponseEvent('end') + + assert.strictEqual(status, 200) + assert.strictEqual(Buffer.from(body).toString(), 'partial') + }) + + it('rejects aborted responses', async () => { + let observations = 0 + let resolveResponseClosed + const responseClosed = new Promise((resolve) => { + resolveResponseClosed = resolve + }) + const server = http.createServer((request, response) => { + response.writeHead(200, { 'content-length': 100 }) + response.write('partial') + response.once('close', resolveResponseClosed) + setImmediate(() => response.destroy()) + }) + transport.setResponseHeaderObserver(() => { + observations++ + }) + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) + try { + const { port } = server.address() + const head = Buffer.from( + `POST / HTTP/1.1\r\nHost: 127.0.0.1:${port}\r\nContent-Length: 0\r\n\r\n`, + ) + const request = transport.httpRequest( + '127.0.0.1', port, false, '', true, 0, head.length, 0, 0, fakeWasmMemory(head), + ) + + await Promise.all([responseClosed, assert.rejects(request, /response aborted/)]) + assert.strictEqual(observations, 0) + } finally { + transport.setResponseHeaderObserver(undefined) + await new Promise(resolve => server.close(resolve)) + } + }) + + it('rejects response errors', async () => { + const error = new Error('response failed') + + await assert.rejects(requestWithResponseEvent('error', error), error) + }) + + it('preserves request errors', async () => { + const error = new Error('request failed') + + await assert.rejects(requestWithResponseEvent('request-error', error), error) + }) + + it('preserves synchronous write errors', async () => { + const error = new Error('write failed') + + await assert.rejects(requestWithResponseEvent('write-error', error), error) + }) +}) + describe('http_transport response header observer', () => { let server let port @@ -61,12 +167,18 @@ describe('http_transport response header observer', () => { it('invokes the observer with the raw response headers', async () => { let observed - transport.setResponseHeaderObserver((rawHeaders) => { + let observations = 0 + + /** @param {string[]} rawHeaders */ + function observeHeaders (rawHeaders) { observed = rawHeaders - }) + observations++ + } + transport.setResponseHeaderObserver(observeHeaders) await doRequest() + assert.strictEqual(observations, 1) assert.ok(Array.isArray(observed), 'observer received the raw headers array') const idx = observed.findIndex(h => h.toLowerCase() === 'datadog-container-tags-hash') assert.notStrictEqual(idx, -1, 'container-tags hash header present') @@ -322,6 +434,7 @@ describe('http_transport request body lifetime', () => { 'utf8', ) const memory = new WebAssembly.Memory({ initial: 1 }) + // eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- WebAssembly.Memory.buffer is the full buffer. const bytes = new Uint8Array(memory.buffer) bytes.set(head, 0) bytes.set(body, head.length) diff --git a/test/pipeline.js b/test/pipeline.js index 6f452916..88fea812 100644 --- a/test/pipeline.js +++ b/test/pipeline.js @@ -178,6 +178,7 @@ class NativeSpansInterface { _refreshViews () { this._cqbView = new DataView(this._wasmMemory.buffer, this._cqbPtr) + // eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- WebAssembly.Memory.buffer is the full buffer. this._cqbBytes = new Uint8Array(this._wasmMemory.buffer, this._cqbPtr) } @@ -458,20 +459,24 @@ describe('pipeline', { skip }, () => { describe('span attributes', () => { it('should set and get string tags', () => { const span = nativeSpans.createSpan() + // eslint-disable-next-line unicorn/prefer-https -- Plain HTTP is the tag value under test. + const httpUrl = 'http://example.com/api' span.setTag('http.method', 'GET') - span.setTag('http.url', 'http://example.com/api') + span.setTag('http.url', httpUrl) assert.strictEqual(span.getTag('http.method'), 'GET') - assert.strictEqual(span.getTag('http.url'), 'http://example.com/api') + assert.strictEqual(span.getTag('http.url'), httpUrl) }) it('should set and get numeric tags', () => { const span = nativeSpans.createSpan() + // eslint-disable-next-line unicorn/prefer-math-constants -- The exact fractional value is the test input. + const metricValue = 3.14159 span.setTag('http.status_code', 200) - span.setTag('custom.metric', 3.141_59) + span.setTag('custom.metric', metricValue) assert.strictEqual(span.getTag('http.status_code'), 200) - assert.strictEqual(span.getTag('custom.metric'), 3.141_59) + assert.strictEqual(span.getTag('custom.metric'), metricValue) }) it('should set and get error state', () => { @@ -730,6 +735,7 @@ describe('pipeline', { skip }, () => { // second entry). const ptr = nativeSpans.state.string_table_input_ptr() const view = new DataView(wasmMemory.buffer, ptr) + // eslint-disable-next-line unicorn/no-unsafe-buffer-conversion -- WebAssembly.Memory.buffer is the full buffer. const bytes = new Uint8Array(wasmMemory.buffer, ptr) const entries = [[60_001, 'bulk-key'], [60_002, 'bulk-val']] let off = 0 diff --git a/test/process-discovery.js b/test/process-discovery.js index 9f202350..69b0e4c9 100644 --- a/test/process-discovery.js +++ b/test/process-discovery.js @@ -8,7 +8,21 @@ const libdatadog = require('..') const process_discovery = libdatadog.load('process-discovery') assert(process_discovery !== undefined) -const metadata = new process_discovery.TracerMetadata( +const metadata = { + runtimeId: '7938685c-19dd-490f-b9b3-8aae4c22f897', + tracerVersion: '1.0.0', + hostname: 'my_hostname', + serviceName: 'my_svc', + serviceEnv: 'my_env', + serviceVersion: 'my_version', + processTags: 'entrypoint.name:server,svc.auto:my_svc', + containerId: 'abc123def456abc123def456abc123def456abc123def456abc123def456abc123', +} + +const cfg_handle = process_discovery.storeMetadata(metadata) +assert(cfg_handle !== undefined) + +const positional_metadata = new process_discovery.TracerMetadata( '7938685c-19dd-490f-b9b3-8aae4c22f897', '1.0.0', 'my_hostname', @@ -18,24 +32,21 @@ const metadata = new process_discovery.TracerMetadata( 'entrypoint.name:server,svc.auto:my_svc', 'abc123def456abc123def456abc123def456abc123def456abc123def456abc123', ) - -const cfg_handle = process_discovery.storeMetadata(metadata) -assert(cfg_handle !== undefined) +const positional_cfg_handle = process_discovery.storeMetadata(positional_metadata) +assert(positional_cfg_handle !== undefined) // Same shape, plus a thread-local metadata block (OTEP-4947). libdatadog // implicitly prepends `datadog.local_root_span_id` at wire index 0 in the // attribute key map; entries here start at wire index 1. `schemaVersion` and // `extraAttributes` describe the on-the-wire record schema for readers. -const metadata_with_threadlocal = new process_discovery.TracerMetadata( - '7938685c-19dd-490f-b9b3-8aae4c22f898', - '1.0.0', - 'my_hostname', - 'my_svc', - 'my_env', - 'my_version', - undefined, - undefined, - { +const metadata_with_threadlocal = { + runtimeId: '7938685c-19dd-490f-b9b3-8aae4c22f898', + tracerVersion: '1.0.0', + hostname: 'my_hostname', + serviceName: 'my_svc', + serviceEnv: 'my_env', + serviceVersion: 'my_version', + threadlocalMetadata: { attributeKeys: ['endpoint', 'http.status'], schemaVersion: 'nodejs_v1_dev', extraAttributes: [ @@ -44,35 +55,21 @@ const metadata_with_threadlocal = new process_discovery.TracerMetadata( { key: 'threadlocal.runtime.name', stringValue: 'nodejs' }, ], }, -) -assert.deepStrictEqual( - metadata_with_threadlocal.threadlocalMetadata.attributeKeys, - ['endpoint', 'http.status'], -) -assert.strictEqual( - metadata_with_threadlocal.threadlocalMetadata.schemaVersion, - 'nodejs_v1_dev', -) -assert.strictEqual( - metadata_with_threadlocal.threadlocalMetadata.extraAttributes.length, - 3, -) +} const cfg_handle_threadlocal = process_discovery.storeMetadata(metadata_with_threadlocal) assert(cfg_handle_threadlocal !== undefined) // An ExtraAttribute with neither stringValue nor intValue set is a caller // error — one of them has to be picked. -const bad_metadata_neither = new process_discovery.TracerMetadata( - '7938685c-19dd-490f-b9b3-8aae4c22f899', - '1.0.0', - 'my_hostname', - undefined, undefined, undefined, undefined, undefined, - { +const bad_metadata_neither = { + runtimeId: '7938685c-19dd-490f-b9b3-8aae4c22f899', + tracerVersion: '1.0.0', + hostname: 'my_hostname', + threadlocalMetadata: { attributeKeys: [], - schemaVersion: undefined, extraAttributes: [{ key: 'threadlocal.bogus' }], }, -) +} assert.throws( () => process_discovery.storeMetadata(bad_metadata_neither), /neither is/, @@ -80,17 +77,15 @@ assert.throws( // Setting both stringValue and intValue is also a caller error — the intent // is ambiguous, so reject. -const bad_metadata_both = new process_discovery.TracerMetadata( - '7938685c-19dd-490f-b9b3-8aae4c22f89a', - '1.0.0', - 'my_hostname', - undefined, undefined, undefined, undefined, undefined, - { +const bad_metadata_both = { + runtimeId: '7938685c-19dd-490f-b9b3-8aae4c22f89a', + tracerVersion: '1.0.0', + hostname: 'my_hostname', + threadlocalMetadata: { attributeKeys: [], - schemaVersion: undefined, extraAttributes: [{ key: 'threadlocal.bogus', stringValue: 's', intValue: 1 }], }, -) +} assert.throws( () => process_discovery.storeMetadata(bad_metadata_both), /both are/, diff --git a/test/wasm/datadog-js-zstd/index.js b/test/wasm/datadog-js-zstd/index.js deleted file mode 100644 index 734dfeb8..00000000 --- a/test/wasm/datadog-js-zstd/index.js +++ /dev/null @@ -1,51 +0,0 @@ -const assert = require('node:assert') - -const loader = require('../../../load') - -const zstd = loader.load('datadog-js-zstd') -assert(zstd !== undefined) - -// Create some compressible data -const SAMPLE_SIZE = 512 -const SAMPLE_COUNT = 1024 -const DATA_SIZE = SAMPLE_COUNT * 4 * SAMPLE_SIZE - -const samples = [] -for (let i = 0; i < SAMPLE_COUNT; i++) { - const sample = Array.from({ length: SAMPLE_SIZE }) - for (let j = 0; j < SAMPLE_SIZE; j++) { - sample[j] = Math.trunc(Math.random() * 256) - } - samples.push(sample) -} -const data = Array.from({ length: DATA_SIZE }) -for (let i = 0; i < DATA_SIZE; i += SAMPLE_SIZE) { - data.push(...samples[Math.trunc(Math.random() * SAMPLE_COUNT)]) -} -// Introduce some irregularities -for (let i = 0; i < SAMPLE_COUNT; i++) { - data[Math.trunc(Math.random() * DATA_SIZE)] = 0 -} -const dataArr = new Uint8Array(data) -const compressed3 = zstd.zstd_compress(dataArr, 3) -ensureCompressed(compressed3) - -// Test that 0 means default compression level -const compressed0 = zstd.zstd_compress(dataArr, 0) -ensureCompressed(compressed3) -assert(compressed0.length == compressed3.length) - -// Test that compression levels are correctly passed on. -// Level 18 should produce a smaller output than level 3. -// We can go all the way up to 22, but it is significantly slower. -const compressed18 = zstd.zstd_compress(dataArr, 18) -ensureCompressed(compressed18) -assert(compressed18.length < compressed3.length) - -function ensureCompressed (compressed) { - assert(compressed.length > 4) - assert.equal(compressed[0], 0x28) - assert.equal(compressed[1], 0xB5) - assert.equal(compressed[2], 0x2F) - assert.equal(compressed[3], 0xFD) -} diff --git a/test/wasm/sketches/index.js b/test/wasm/sketches/index.js deleted file mode 100644 index dfae84f6..00000000 --- a/test/wasm/sketches/index.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict' - -const assert = require('node:assert') - -const loader = require('../../../load') -const { DDSketch } = loader.load('sketches') - -const sketch = new DDSketch() -assert.strictEqual(sketch.count(), 0) - -sketch.add(1) -sketch.addWithCount(2, 3) -assert.strictEqual(sketch.count(), 4) - -assert.throws(() => sketch.add(-1), /point is invalid/) -assert.throws(() => sketch.addWithCount(1, Number.NaN), /count is invalid/) - -const encoded = sketch.encode() -assert(encoded instanceof Uint8Array) -assert(encoded.length > 0) - -assert.strictEqual(sketch.count(), 4) -sketch.add(3) -assert.strictEqual(sketch.count(), 5) - -const reencoded = sketch.encode() -assert(reencoded instanceof Uint8Array) -assert(reencoded.length > 0) -assert.strictEqual(sketch.count(), 5) diff --git a/wasm-rust-toolchain b/wasm-rust-toolchain new file mode 100644 index 00000000..ee9644bc --- /dev/null +++ b/wasm-rust-toolchain @@ -0,0 +1 @@ +nightly-2026-07-26 diff --git a/yarn.lock b/yarn.lock index d8f55992..fa5b8f38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@babel/helper-validator-identifier@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" - integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== - "@emnapi/core@^1.4.3": version "1.8.1" resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.8.1.tgz#fd9efe721a616288345ffee17a1f26ac5dd01349" @@ -29,13 +24,20 @@ dependencies: tslib "^2.4.0" -"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.5.0", "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.0", "@eslint-community/eslint-utils@^4.9.1": +"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.5.0", "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" +"@eslint-community/eslint-utils@^4.10.1": + version "4.10.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6" + integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg== + dependencies: + eslint-visitor-keys "^3.4.3" + "@eslint-community/regexpp@^4.11.0", "@eslint-community/regexpp@^4.12.2": version "4.12.2" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" @@ -50,10 +52,10 @@ debug "^4.3.1" minimatch "^10.2.4" -"@eslint/config-helpers@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.6.0.tgz#ef9a36881d39dfd5dbeac22b0da997fabfb08b03" - integrity sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA== +"@eslint/config-helpers@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.7.0.tgz#09ee4aa07b73f059ec2d4c74bf4b2ff02b322377" + integrity sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw== dependencies: "@eslint/core" "^1.2.1" @@ -64,6 +66,14 @@ dependencies: "@types/json-schema" "^7.0.15" +"@eslint/css-tree@^4.0.5": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@eslint/css-tree/-/css-tree-4.1.0.tgz#3ad72d9e51779e1fd8550d67868ac1e94a27b39b" + integrity sha512-cg0ohyrAG3swyGqt8t1K/OK97DqBw/ftDvlvyY1fmEst5B40UOmsimwLENq74z2dyw5CDM+3zJIW+CV2nFNDdA== + dependencies: + mdn-data "2.34.0" + source-map-js "^1.2.1" + "@eslint/js@^10.0.1": version "10.0.1" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583" @@ -275,67 +285,65 @@ balanced-match@^4.0.2: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== -baseline-browser-mapping@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9" - integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA== +baseline-browser-mapping@^2.11.12: + version "2.11.20" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz#26078c7a4b08299656ea7ddceaebec955dc44303" + integrity sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw== -brace-expansion@^5.0.2: +brace-expansion@^5.0.8: version "5.0.9" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.9.tgz#7c72438809b5fa5babf54199a1f1c281a6984fcf" integrity sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg== dependencies: balanced-match "^4.0.2" -browserslist@^4.28.1: - version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" - integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== +browserslist@^4.28.7, browserslist@^4.28.8: + version "4.28.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.8.tgz#a3c79ceb70028527e5da7dafc887f3200b5168c0" + integrity sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA== dependencies: - baseline-browser-mapping "^2.9.0" - caniuse-lite "^1.0.30001759" - electron-to-chromium "^1.5.263" - node-releases "^2.0.27" - update-browserslist-db "^1.2.0" + baseline-browser-mapping "^2.11.12" + caniuse-lite "^1.0.30001809" + electron-to-chromium "^1.5.402" + node-releases "^2.0.53" + update-browserslist-db "^1.3.0" builtin-modules@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-5.0.0.tgz#9be95686dedad2e9eed05592b07733db87dcff1a" integrity sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg== -caniuse-lite@^1.0.30001759: - version "1.0.30001777" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz#028f21e4b2718d138b55e692583e6810ccf60691" - integrity sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ== +caniuse-lite@^1.0.30001809: + version "1.0.30001810" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz#4970b477dea3278374de9bc43aa8f5d39fc3cda2" + integrity sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg== change-case@^5.4.4: version "5.4.4" resolved "https://registry.yarnpkg.com/change-case/-/change-case-5.4.4.tgz#0d52b507d8fb8f204343432381d1a6d7bff97a02" integrity sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w== -ci-info@^4.3.1: +ci-info@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== -clean-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" - integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== - dependencies: - escape-string-regexp "^1.0.5" - comment-parser@^1.4.1: version "1.4.5" resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.5.tgz#6c595cd090737a1010fe5ff40d86e1d21b7bd6ce" integrity sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw== -core-js-compat@^3.46.0: - version "3.48.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.48.0.tgz#7efbe1fc1cbad44008190462217cc5558adaeaa6" - integrity sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q== +convert-hrtime@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/convert-hrtime/-/convert-hrtime-5.0.0.tgz#f2131236d4598b95de856926a67100a0a97e9fa3" + integrity sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg== + +core-js-compat@^3.50.0: + version "3.50.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.50.0.tgz#d5922c2a692ab1cba6078c920e7c5567421ae08e" + integrity sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q== dependencies: - browserslist "^4.28.1" + browserslist "^4.28.7" cross-spawn@^7.0.6: version "7.0.6" @@ -358,10 +366,15 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -electron-to-chromium@^1.5.263: - version "1.5.307" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz#09f8973100c39fb0d003b890393cd1d58932b1c8" - integrity sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg== +detect-indent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-7.0.2.tgz#16c516bf75d4b2f759f68214554996d467c8d648" + integrity sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A== + +electron-to-chromium@^1.5.402: + version "1.5.420" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz#fc66d26a722d6f227e2092acdf38dd55b198cb44" + integrity sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA== enhanced-resolve@^5.17.1: version "5.20.0" @@ -371,16 +384,16 @@ enhanced-resolve@^5.17.1: graceful-fs "^4.2.4" tapable "^2.3.0" +entities@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-8.0.0.tgz#c1df5fe3602429747fa233d0dd26f142f0ce4743" + integrity sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA== + escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -425,10 +438,10 @@ eslint-plugin-import-x@^4.17.1: stable-hash-x "^0.2.0" unrs-resolver "^1.9.2" -eslint-plugin-n@^17.24.0: - version "17.24.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz#b66fa05f7a6c1ba16768f0921b8974147dddd060" - integrity sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw== +eslint-plugin-n@^18.2.2: + version "18.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-18.2.2.tgz#743c7931831472f3378724cc32ffe3917c734819" + integrity sha512-gOO0lIqwEjZ750kv9/SptCWArUoAZXJoBr0vYWTO2dCBxctHUXlBIigiC8xuxxr/NKqgIT6Ehz1xRcilj8a5cA== dependencies: "@eslint-community/eslint-utils" "^4.5.0" enhanced-resolve "^5.17.1" @@ -438,29 +451,32 @@ eslint-plugin-n@^17.24.0: globrex "^0.1.2" ignore "^5.3.2" semver "^7.6.3" - ts-declaration-location "^1.0.6" -eslint-plugin-unicorn@^63.0.0: - version "63.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-63.0.0.tgz#db210b87bb66f0f15ab675ba13d9f1fb61016b22" - integrity sha512-Iqecl9118uQEXYh7adylgEmGfkn5es3/mlQTLLkd4pXkIk9CTGrAbeUux+YljSa2ohXCBmQQ0+Ej1kZaFgcfkA== +eslint-plugin-unicorn@^74.0.0: + version "74.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-74.0.0.tgz#9b423103108d404971f8730816c9fc14182a97ae" + integrity sha512-AGnsGi2SxHg1HEAXxn9nSnZfyjvTWkxm8E8hpd/9tD6dLjBUdcD7+D6ZN64HmmCXTSXlrwVyUqe20Uyb2CaurA== dependencies: - "@babel/helper-validator-identifier" "^7.28.5" - "@eslint-community/eslint-utils" "^4.9.0" + "@eslint-community/eslint-utils" "^4.10.1" + "@eslint/css-tree" "^4.0.5" + browserslist "^4.28.8" change-case "^5.4.4" - ci-info "^4.3.1" - clean-regexp "^1.0.0" - core-js-compat "^3.46.0" + ci-info "^4.4.0" + core-js-compat "^3.50.0" + detect-indent "^7.0.2" + entities "^8.0.0" find-up-simple "^1.0.1" - globals "^16.4.0" + globals "^17.11.0" indent-string "^5.0.0" is-builtin-module "^5.0.0" - jsesc "^3.1.0" + is-identifier "^1.1.0" pluralize "^8.0.0" - regexp-tree "^0.1.27" - regjsparser "^0.13.0" - semver "^7.7.3" + quote-js-string "^0.1.0" + regjsparser "^0.13.2" + reserved-identifiers "^1.2.0" + semver "^7.8.5" strip-indent "^4.1.1" + yaml "^2.9.0" eslint-scope@^9.1.2: version "9.1.2" @@ -487,15 +503,15 @@ eslint-visitor-keys@^5.0.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== -eslint@^10.6.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.6.0.tgz#e1b4059c582be950c7088c9b55f984738b243c27" - integrity sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg== +eslint@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.9.1.tgz#409da5c41a5536d5a849f8555a18ca7ef1eb963b" + integrity sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A== dependencies: "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.2" "@eslint/config-array" "^0.23.5" - "@eslint/config-helpers" "^0.6.0" + "@eslint/config-helpers" "^0.7.0" "@eslint/core" "^1.2.1" "@eslint/plugin-kit" "^0.7.2" "@humanfs/node" "^0.16.6" @@ -519,7 +535,7 @@ eslint@^10.6.0: imurmurhash "^0.1.4" is-glob "^4.0.0" json-stable-stringify-without-jsonify "^1.0.1" - minimatch "^10.2.4" + minimatch "^10.2.5" natural-compare "^1.4.0" optionator "^0.9.3" @@ -613,6 +629,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== +function-timeout@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/function-timeout/-/function-timeout-1.0.2.tgz#e5a7b6ffa523756ff20e1231bbe37b5f373aadd5" + integrity sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA== + get-tsconfig@^4.10.1, get-tsconfig@^4.8.1: version "4.13.6" resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.6.tgz#2fbfda558a98a691a798f123afd95915badce876" @@ -632,15 +653,10 @@ globals@^15.11.0: resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8" integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== -globals@^16.4.0: - version "16.5.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-16.5.0.tgz#ccf1594a437b97653b2be13ed4d8f5c9f850cac1" - integrity sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ== - -globals@^17.7.0: - version "17.7.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-17.7.0.tgz#553d55090b4dde8209ec2da42580d6e7e7d8b10d" - integrity sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg== +globals@^17.11.0: + version "17.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.11.0.tgz#d643485bb30220d7751e511cf4f68c73d3870d87" + integrity sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw== globrex@^0.1.2: version "0.1.2" @@ -652,6 +668,13 @@ graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +identifier-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/identifier-regex/-/identifier-regex-1.1.0.tgz#042abfaf28db15223436661f3b9ec882649f6ef0" + integrity sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA== + dependencies: + reserved-identifiers "^1.0.0" + ignore@^5.2.0, ignore@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -686,12 +709,20 @@ is-glob@^4.0.0, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-identifier@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-identifier/-/is-identifier-1.1.0.tgz#6b406e15a429f7196f6496e09f8333bcb4b2db1b" + integrity sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw== + dependencies: + identifier-regex "^1.1.0" + super-regex "^1.1.0" + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -jsesc@^3.1.0, jsesc@~3.1.0: +jsesc@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== @@ -733,12 +764,26 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -minimatch@^10.2.4, "minimatch@^9.0.3 || ^10.1.2": - version "10.2.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" - integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== +make-asynchronous@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/make-asynchronous/-/make-asynchronous-1.1.0.tgz#6225f7f1ccaab9acaac5e2fcd0b075afefff19aa" + integrity sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg== dependencies: - brace-expansion "^5.0.2" + p-event "^6.0.0" + type-fest "^4.6.0" + web-worker "^1.5.0" + +mdn-data@2.34.0: + version "2.34.0" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.34.0.tgz#414f99ec5c9001ef74981b1545c2ca34f0bdaeb9" + integrity sha512-OgIlLv0NxJKVW4GTSAoEgpRGd4F2XCqGinK0MsMlBCCS/Zcm2/LsbercNWNA7PeMMcjl75NnI97eqyo7zkdxWA== + +minimatch@^10.2.4, minimatch@^10.2.5, "minimatch@^9.0.3 || ^10.1.2": + version "10.2.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef" + integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A== + dependencies: + brace-expansion "^5.0.8" ms@^2.1.3: version "2.1.3" @@ -755,10 +800,10 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-releases@^2.0.27: - version "2.0.36" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d" - integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA== +node-releases@^2.0.53: + version "2.0.54" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.54.tgz#09af17d5647aa9f221ec5cf2becb95b68a981afe" + integrity sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ== optionator@^0.9.3: version "0.9.4" @@ -772,6 +817,13 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +p-event@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-6.0.1.tgz#8f62a1e3616d4bc01fce3abda127e0383ef4715b" + integrity sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w== + dependencies: + p-timeout "^6.1.2" + p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -786,6 +838,11 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-timeout@^6.1.2: + version "6.1.4" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.1.4.tgz#418e1f4dd833fa96a2e3f532547dd2abdb08dbc2" + integrity sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -801,7 +858,7 @@ picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^4.0.2, picomatch@^4.0.3: +picomatch@^4.0.3: version "4.0.4" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== @@ -821,28 +878,38 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -regexp-tree@^0.1.27: - version "0.1.27" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" - integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== +quote-js-string@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/quote-js-string/-/quote-js-string-0.1.0.tgz#812aef957a2dfb31d32f0d9e9662fe558bc9842b" + integrity sha512-Y3NoRtprEEZQD8RfxMCfS0ZTqc4e+i18OrXEXAvpM6TfC/3y+0L5rNbZiSnbBBEkDfFzbpd8o+cE8q3/anjMGA== -regjsparser@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.0.tgz#01f8351335cf7898d43686bc74d2dd71c847ecc0" - integrity sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q== +regjsparser@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.2.tgz#f654734b5c588b22ba3e21693b30523417180808" + integrity sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ== dependencies: jsesc "~3.1.0" +reserved-identifiers@^1.0.0, reserved-identifiers@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz#d2982cd698e317dd3dced1ee1c52412dbd64fc64" + integrity sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw== + resolve-pkg-maps@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== -semver@^7.5.4, semver@^7.6.3, semver@^7.7.2, semver@^7.7.3: +semver@^7.5.4, semver@^7.6.3, semver@^7.7.2: version "7.7.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== +semver@^7.8.5: + version "7.8.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" + integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -855,6 +922,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + stable-hash-x@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/stable-hash-x/-/stable-hash-x-0.2.0.tgz#dfd76bfa5d839a7470125c6a6b3c8b22061793e9" @@ -865,17 +937,26 @@ strip-indent@^4.1.1: resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.1.1.tgz#aba13de189d4ad9a17f6050e76554ac27585c7af" integrity sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA== +super-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/super-regex/-/super-regex-1.1.0.tgz#14b69b6374f7b3338db52ecd511dae97c27acf75" + integrity sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ== + dependencies: + function-timeout "^1.0.1" + make-asynchronous "^1.0.1" + time-span "^5.1.0" + tapable@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== -ts-declaration-location@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz#d4068fe9975828b3b453b3ab112b4711d8267688" - integrity sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA== +time-span@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/time-span/-/time-span-5.1.0.tgz#80c76cf5a0ca28e0842d3f10a4e99034ce94b90d" + integrity sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA== dependencies: - picomatch "^4.0.2" + convert-hrtime "^5.0.0" tslib@^2.4.0: version "2.8.1" @@ -889,6 +970,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-fest@^4.6.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + unrs-resolver@^1.9.2: version "1.11.1" resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" @@ -916,10 +1002,10 @@ unrs-resolver@^1.9.2: "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" -update-browserslist-db@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" - integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== +update-browserslist-db@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz#9d99fbff56c50bb11ba5fd35cece5916da595836" + integrity sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw== dependencies: escalade "^3.2.0" picocolors "^1.1.1" @@ -931,6 +1017,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +web-worker@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.5.0.tgz#71b2b0fbcc4293e8f0aa4f6b8a3ffebff733dcc5" + integrity sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw== + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -943,6 +1034,11 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +yaml@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4" + integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"