heaplens-cli 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +43 -2
- package/THIRD_PARTY_NOTICES.json +2199 -0
- package/bin/heaplens.cjs +72 -0
- package/distribution.json +1754 -0
- package/licenses/d3-LICENSE.txt +12 -0
- package/licenses/rust/aho-corasick-1.1.4/COPYING +3 -0
- package/licenses/rust/aho-corasick-1.1.4/LICENSE-MIT +21 -0
- package/licenses/rust/anstream-0.6.21/LICENSE-APACHE +202 -0
- package/licenses/rust/anstream-0.6.21/LICENSE-MIT +19 -0
- package/licenses/rust/anstyle-1.0.13/LICENSE-APACHE +202 -0
- package/licenses/rust/anstyle-1.0.13/LICENSE-MIT +19 -0
- package/licenses/rust/anstyle-parse-0.2.7/LICENSE-APACHE +202 -0
- package/licenses/rust/anstyle-parse-0.2.7/LICENSE-MIT +19 -0
- package/licenses/rust/anstyle-query-1.1.5/LICENSE-APACHE +202 -0
- package/licenses/rust/anstyle-query-1.1.5/LICENSE-MIT +19 -0
- package/licenses/rust/anyhow-1.0.100/LICENSE-APACHE +176 -0
- package/licenses/rust/anyhow-1.0.100/LICENSE-MIT +23 -0
- package/licenses/rust/arrayvec-0.5.2/LICENSE-APACHE +201 -0
- package/licenses/rust/arrayvec-0.5.2/LICENSE-MIT +25 -0
- package/licenses/rust/arrayvec-0.7.8/LICENSE-APACHE +201 -0
- package/licenses/rust/arrayvec-0.7.8/LICENSE-MIT +25 -0
- package/licenses/rust/atomic-waker-1.1.2/LICENSE-APACHE +201 -0
- package/licenses/rust/atomic-waker-1.1.2/LICENSE-MIT +23 -0
- package/licenses/rust/atomic-waker-1.1.2/LICENSE-THIRD-PARTY +45 -0
- package/licenses/rust/autocfg-1.5.1/LICENSE-APACHE +201 -0
- package/licenses/rust/autocfg-1.5.1/LICENSE-MIT +25 -0
- package/licenses/rust/base64-0.22.1/LICENSE-APACHE +201 -0
- package/licenses/rust/base64-0.22.1/LICENSE-MIT +21 -0
- package/licenses/rust/base64-0.23.1/LICENSE-APACHE +201 -0
- package/licenses/rust/base64-0.23.1/LICENSE-MIT +21 -0
- package/licenses/rust/bitflags-1.3.2/LICENSE-APACHE +201 -0
- package/licenses/rust/bitflags-1.3.2/LICENSE-MIT +25 -0
- package/licenses/rust/bitflags-2.10.0/LICENSE-APACHE +201 -0
- package/licenses/rust/bitflags-2.10.0/LICENSE-MIT +25 -0
- package/licenses/rust/blake3-1.8.7/LICENSE_A2 +202 -0
- package/licenses/rust/blake3-1.8.7/LICENSE_A2LLVM +219 -0
- package/licenses/rust/blake3-1.8.7/LICENSE_CC0 +121 -0
- package/licenses/rust/bytes-1.11.0/LICENSE +25 -0
- package/licenses/rust/cc-1.6.0/LICENSE-APACHE +201 -0
- package/licenses/rust/cc-1.6.0/LICENSE-MIT +25 -0
- package/licenses/rust/cfg-if-1.0.4/LICENSE-APACHE +201 -0
- package/licenses/rust/cfg-if-1.0.4/LICENSE-MIT +25 -0
- package/licenses/rust/cfg_aliases-0.2.2/LICENSE +9 -0
- package/licenses/rust/cfg_aliases-0.2.2/NOTICES.md +26 -0
- package/licenses/rust/chacha20-0.10.2/LICENSE-APACHE +201 -0
- package/licenses/rust/chacha20-0.10.2/LICENSE-MIT +25 -0
- package/licenses/rust/chrono-0.4.45/LICENSE.txt +240 -0
- package/licenses/rust/clap-4.5.60/LICENSE-APACHE +202 -0
- package/licenses/rust/clap-4.5.60/LICENSE-MIT +19 -0
- package/licenses/rust/clap_builder-4.5.60/LICENSE-APACHE +202 -0
- package/licenses/rust/clap_builder-4.5.60/LICENSE-MIT +19 -0
- package/licenses/rust/clap_derive-4.5.55/LICENSE-APACHE +202 -0
- package/licenses/rust/clap_derive-4.5.55/LICENSE-MIT +19 -0
- package/licenses/rust/clap_lex-1.0.0/LICENSE-APACHE +202 -0
- package/licenses/rust/clap_lex-1.0.0/LICENSE-MIT +19 -0
- package/licenses/rust/colorchoice-1.0.4/LICENSE-APACHE +202 -0
- package/licenses/rust/colorchoice-1.0.4/LICENSE-MIT +19 -0
- package/licenses/rust/constant_time_eq-0.4.2/LICENSE-APACHE +176 -0
- package/licenses/rust/constant_time_eq-0.4.2/LICENSE-CC0 +121 -0
- package/licenses/rust/constant_time_eq-0.4.2/LICENSE-MIT0 +14 -0
- package/licenses/rust/core-foundation-sys-0.8.7/LICENSE-APACHE +201 -0
- package/licenses/rust/core-foundation-sys-0.8.7/LICENSE-MIT +25 -0
- package/licenses/rust/crossbeam-deque-0.8.6/LICENSE-APACHE +201 -0
- package/licenses/rust/crossbeam-deque-0.8.6/LICENSE-MIT +27 -0
- package/licenses/rust/crossbeam-epoch-0.9.18/LICENSE-APACHE +201 -0
- package/licenses/rust/crossbeam-epoch-0.9.18/LICENSE-MIT +27 -0
- package/licenses/rust/crossbeam-utils-0.8.21/LICENSE-APACHE +201 -0
- package/licenses/rust/crossbeam-utils-0.8.21/LICENSE-MIT +27 -0
- package/licenses/rust/displaydoc-0.2.7/LICENSE-APACHE +201 -0
- package/licenses/rust/displaydoc-0.2.7/LICENSE-MIT +23 -0
- package/licenses/rust/either-1.15.0/LICENSE-APACHE +201 -0
- package/licenses/rust/either-1.15.0/LICENSE-MIT +25 -0
- package/licenses/rust/env_filter-0.1.4/LICENSE-APACHE +202 -0
- package/licenses/rust/env_filter-0.1.4/LICENSE-MIT +19 -0
- package/licenses/rust/env_logger-0.11.8/LICENSE-APACHE +202 -0
- package/licenses/rust/env_logger-0.11.8/LICENSE-MIT +19 -0
- package/licenses/rust/equivalent-1.0.2/LICENSE-APACHE +201 -0
- package/licenses/rust/equivalent-1.0.2/LICENSE-MIT +25 -0
- package/licenses/rust/errno-0.3.14/LICENSE-APACHE +201 -0
- package/licenses/rust/errno-0.3.14/LICENSE-MIT +25 -0
- package/licenses/rust/fastrand-2.3.0/LICENSE-APACHE +201 -0
- package/licenses/rust/fastrand-2.3.0/LICENSE-MIT +23 -0
- package/licenses/rust/find-msvc-tools-0.1.14/LICENSE-APACHE +201 -0
- package/licenses/rust/find-msvc-tools-0.1.14/LICENSE-MIT +25 -0
- package/licenses/rust/fixedbitset-0.4.2/LICENSE-APACHE +201 -0
- package/licenses/rust/fixedbitset-0.4.2/LICENSE-MIT +25 -0
- package/licenses/rust/form_urlencoded-1.2.2/LICENSE-APACHE +201 -0
- package/licenses/rust/form_urlencoded-1.2.2/LICENSE-MIT +25 -0
- package/licenses/rust/futures-channel-0.3.34/LICENSE-APACHE +202 -0
- package/licenses/rust/futures-channel-0.3.34/LICENSE-MIT +26 -0
- package/licenses/rust/futures-core-0.3.34/LICENSE-APACHE +202 -0
- package/licenses/rust/futures-core-0.3.34/LICENSE-MIT +26 -0
- package/licenses/rust/futures-io-0.3.34/LICENSE-APACHE +202 -0
- package/licenses/rust/futures-io-0.3.34/LICENSE-MIT +26 -0
- package/licenses/rust/futures-sink-0.3.34/LICENSE-APACHE +202 -0
- package/licenses/rust/futures-sink-0.3.34/LICENSE-MIT +26 -0
- package/licenses/rust/futures-task-0.3.34/LICENSE-APACHE +202 -0
- package/licenses/rust/futures-task-0.3.34/LICENSE-MIT +26 -0
- package/licenses/rust/futures-util-0.3.34/LICENSE-APACHE +202 -0
- package/licenses/rust/futures-util-0.3.34/LICENSE-MIT +26 -0
- package/licenses/rust/getrandom-0.2.17/LICENSE-APACHE +201 -0
- package/licenses/rust/getrandom-0.2.17/LICENSE-MIT +26 -0
- package/licenses/rust/getrandom-0.3.4/LICENSE-APACHE +201 -0
- package/licenses/rust/getrandom-0.3.4/LICENSE-MIT +26 -0
- package/licenses/rust/getrandom-0.4.3/LICENSE-APACHE +201 -0
- package/licenses/rust/getrandom-0.4.3/LICENSE-MIT +26 -0
- package/licenses/rust/getset-0.0.9/LICENSE +21 -0
- package/licenses/rust/hashbrown-0.16.1/LICENSE-APACHE +201 -0
- package/licenses/rust/hashbrown-0.16.1/LICENSE-MIT +25 -0
- package/licenses/rust/heck-0.3.3/LICENSE-APACHE +201 -0
- package/licenses/rust/heck-0.3.3/LICENSE-MIT +25 -0
- package/licenses/rust/heck-0.5.0/LICENSE-APACHE +201 -0
- package/licenses/rust/heck-0.5.0/LICENSE-MIT +25 -0
- package/licenses/rust/http-1.5.0/LICENSE-APACHE +201 -0
- package/licenses/rust/http-1.5.0/LICENSE-MIT +25 -0
- package/licenses/rust/http-body-1.1.0/LICENSE +25 -0
- package/licenses/rust/http-body-util-0.1.5/LICENSE +25 -0
- package/licenses/rust/httparse-1.10.1/LICENSE-APACHE +201 -0
- package/licenses/rust/httparse-1.10.1/LICENSE-MIT +20 -0
- package/licenses/rust/hyper-1.12.0/LICENSE +19 -0
- package/licenses/rust/hyper-rustls-0.27.10/LICENSE-APACHE +201 -0
- package/licenses/rust/hyper-rustls-0.27.10/LICENSE-ISC +15 -0
- package/licenses/rust/hyper-rustls-0.27.10/LICENSE-MIT +25 -0
- package/licenses/rust/hyper-util-0.1.21/LICENSE +19 -0
- package/licenses/rust/iana-time-zone-0.1.65/LICENSE-APACHE +201 -0
- package/licenses/rust/iana-time-zone-0.1.65/LICENSE-MIT +25 -0
- package/licenses/rust/icu_collections-2.3.0/LICENSE +46 -0
- package/licenses/rust/icu_locale_core-2.3.0/LICENSE +46 -0
- package/licenses/rust/icu_normalizer-2.3.0/LICENSE +46 -0
- package/licenses/rust/icu_normalizer_data-2.3.0/LICENSE +46 -0
- package/licenses/rust/icu_properties-2.3.0/LICENSE +46 -0
- package/licenses/rust/icu_properties_data-2.3.0/LICENSE +46 -0
- package/licenses/rust/icu_provider-2.3.1/LICENSE +46 -0
- package/licenses/rust/idna-1.1.0/LICENSE-APACHE +201 -0
- package/licenses/rust/idna-1.1.0/LICENSE-MIT +25 -0
- package/licenses/rust/idna_adapter-1.2.2/LICENSE-APACHE +201 -0
- package/licenses/rust/idna_adapter-1.2.2/LICENSE-MIT +25 -0
- package/licenses/rust/indexmap-2.13.0/LICENSE-APACHE +201 -0
- package/licenses/rust/indexmap-2.13.0/LICENSE-MIT +25 -0
- package/licenses/rust/ipnet-2.12.2/LICENSE-APACHE +201 -0
- package/licenses/rust/ipnet-2.12.2/LICENSE-MIT +7 -0
- package/licenses/rust/is_terminal_polyfill-1.70.2/LICENSE-APACHE +202 -0
- package/licenses/rust/is_terminal_polyfill-1.70.2/LICENSE-MIT +19 -0
- package/licenses/rust/itoa-1.0.17/LICENSE-APACHE +176 -0
- package/licenses/rust/itoa-1.0.17/LICENSE-MIT +23 -0
- package/licenses/rust/jiff-0.2.18/COPYING +3 -0
- package/licenses/rust/jiff-0.2.18/LICENSE-MIT +21 -0
- package/licenses/rust/jvm-hprof-0.1.0/LICENSE.txt +36 -0
- package/licenses/rust/lexical-core-0.7.6/LICENSE-APACHE +201 -0
- package/licenses/rust/lexical-core-0.7.6/LICENSE-MIT +23 -0
- package/licenses/rust/libc-0.2.180/LICENSE-APACHE +176 -0
- package/licenses/rust/libc-0.2.180/LICENSE-MIT +25 -0
- package/licenses/rust/litemap-0.8.3/LICENSE +46 -0
- package/licenses/rust/lock_api-0.4.14/LICENSE-APACHE +201 -0
- package/licenses/rust/lock_api-0.4.14/LICENSE-MIT +25 -0
- package/licenses/rust/log-0.4.29/LICENSE-APACHE +201 -0
- package/licenses/rust/log-0.4.29/LICENSE-MIT +25 -0
- package/licenses/rust/lru-slab-0.1.3/LICENSE-APACHE +202 -0
- package/licenses/rust/lru-slab-0.1.3/LICENSE-MIT +7 -0
- package/licenses/rust/lru-slab-0.1.3/LICENSE-ZLIB +19 -0
- package/licenses/rust/memchr-2.7.6/COPYING +3 -0
- package/licenses/rust/memchr-2.7.6/LICENSE-MIT +21 -0
- package/licenses/rust/memmap2-0.9.9/LICENSE-APACHE +201 -0
- package/licenses/rust/memmap2-0.9.9/LICENSE-MIT +26 -0
- package/licenses/rust/mio-1.1.1/LICENSE +19 -0
- package/licenses/rust/nom-5.1.3/LICENSE +20 -0
- package/licenses/rust/num-traits-0.2.19/LICENSE-APACHE +201 -0
- package/licenses/rust/num-traits-0.2.19/LICENSE-MIT +25 -0
- package/licenses/rust/once_cell-1.21.3/LICENSE-APACHE +201 -0
- package/licenses/rust/once_cell-1.21.3/LICENSE-MIT +23 -0
- package/licenses/rust/parking_lot-0.12.5/LICENSE-APACHE +201 -0
- package/licenses/rust/parking_lot-0.12.5/LICENSE-MIT +25 -0
- package/licenses/rust/parking_lot_core-0.9.12/LICENSE-APACHE +201 -0
- package/licenses/rust/parking_lot_core-0.9.12/LICENSE-MIT +25 -0
- package/licenses/rust/percent-encoding-2.3.2/LICENSE-APACHE +201 -0
- package/licenses/rust/percent-encoding-2.3.2/LICENSE-MIT +25 -0
- package/licenses/rust/petgraph-0.6.5/LICENSE-APACHE +201 -0
- package/licenses/rust/petgraph-0.6.5/LICENSE-MIT +25 -0
- package/licenses/rust/pin-project-lite-0.2.16/LICENSE-APACHE +177 -0
- package/licenses/rust/pin-project-lite-0.2.16/LICENSE-MIT +23 -0
- package/licenses/rust/potential_utf-0.1.6/LICENSE +46 -0
- package/licenses/rust/proc-macro2-1.0.105/LICENSE-APACHE +176 -0
- package/licenses/rust/proc-macro2-1.0.105/LICENSE-MIT +23 -0
- package/licenses/rust/quinn-0.11.12/LICENSE-APACHE +201 -0
- package/licenses/rust/quinn-0.11.12/LICENSE-MIT +7 -0
- package/licenses/rust/quinn-proto-0.11.19/LICENSE-APACHE +201 -0
- package/licenses/rust/quinn-proto-0.11.19/LICENSE-MIT +7 -0
- package/licenses/rust/quinn-udp-0.5.16/LICENSE-APACHE +201 -0
- package/licenses/rust/quinn-udp-0.5.16/LICENSE-MIT +7 -0
- package/licenses/rust/quote-1.0.47/LICENSE-APACHE +176 -0
- package/licenses/rust/quote-1.0.47/LICENSE-MIT +23 -0
- package/licenses/rust/rand-0.10.3/COPYRIGHT +12 -0
- package/licenses/rust/rand-0.10.3/LICENSE-APACHE +176 -0
- package/licenses/rust/rand-0.10.3/LICENSE-MIT +26 -0
- package/licenses/rust/rand_core-0.10.1/COPYRIGHT +9 -0
- package/licenses/rust/rand_core-0.10.1/LICENSE-APACHE +187 -0
- package/licenses/rust/rand_core-0.10.1/LICENSE-MIT +25 -0
- package/licenses/rust/rand_pcg-0.10.2/COPYRIGHT +12 -0
- package/licenses/rust/rand_pcg-0.10.2/LICENSE-APACHE +187 -0
- package/licenses/rust/rand_pcg-0.10.2/LICENSE-MIT +26 -0
- package/licenses/rust/rayon-1.11.0/LICENSE-APACHE +201 -0
- package/licenses/rust/rayon-1.11.0/LICENSE-MIT +25 -0
- package/licenses/rust/rayon-core-1.13.0/LICENSE-APACHE +201 -0
- package/licenses/rust/rayon-core-1.13.0/LICENSE-MIT +25 -0
- package/licenses/rust/regex-1.12.2/LICENSE-APACHE +201 -0
- package/licenses/rust/regex-1.12.2/LICENSE-MIT +25 -0
- package/licenses/rust/regex-automata-0.4.13/LICENSE-APACHE +201 -0
- package/licenses/rust/regex-automata-0.4.13/LICENSE-MIT +25 -0
- package/licenses/rust/regex-syntax-0.8.8/LICENSE-APACHE +201 -0
- package/licenses/rust/regex-syntax-0.8.8/LICENSE-MIT +25 -0
- package/licenses/rust/reqwest-0.12.28/LICENSE-APACHE +201 -0
- package/licenses/rust/reqwest-0.12.28/LICENSE-MIT +20 -0
- package/licenses/rust/ring-0.17.14/LICENSE +9 -0
- package/licenses/rust/ring-0.17.14/LICENSE-BoringSSL +272 -0
- package/licenses/rust/ring-0.17.14/LICENSE-other-bits +13 -0
- package/licenses/rust/rustc-hash-2.1.3/LICENSE-APACHE +176 -0
- package/licenses/rust/rustc-hash-2.1.3/LICENSE-MIT +23 -0
- package/licenses/rust/rustix-1.1.3/COPYRIGHT +29 -0
- package/licenses/rust/rustix-1.1.3/LICENSE-APACHE +201 -0
- package/licenses/rust/rustix-1.1.3/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
- package/licenses/rust/rustix-1.1.3/LICENSE-MIT +23 -0
- package/licenses/rust/rustls-0.23.45/LICENSE-APACHE +201 -0
- package/licenses/rust/rustls-0.23.45/LICENSE-ISC +15 -0
- package/licenses/rust/rustls-0.23.45/LICENSE-MIT +25 -0
- package/licenses/rust/rustls-pki-types-1.15.1/LICENSE-APACHE +201 -0
- package/licenses/rust/rustls-pki-types-1.15.1/LICENSE-MIT +25 -0
- package/licenses/rust/rustls-webpki-0.103.15/LICENSE +19 -0
- package/licenses/rust/ryu-1.0.22/LICENSE-APACHE +176 -0
- package/licenses/rust/ryu-1.0.22/LICENSE-BOOST +23 -0
- package/licenses/rust/scopeguard-1.2.0/LICENSE-APACHE +201 -0
- package/licenses/rust/scopeguard-1.2.0/LICENSE-MIT +25 -0
- package/licenses/rust/serde-1.0.228/LICENSE-APACHE +176 -0
- package/licenses/rust/serde-1.0.228/LICENSE-MIT +23 -0
- package/licenses/rust/serde_core-1.0.228/LICENSE-APACHE +176 -0
- package/licenses/rust/serde_core-1.0.228/LICENSE-MIT +23 -0
- package/licenses/rust/serde_derive-1.0.228/LICENSE-APACHE +176 -0
- package/licenses/rust/serde_derive-1.0.228/LICENSE-MIT +23 -0
- package/licenses/rust/serde_json-1.0.149/LICENSE-APACHE +176 -0
- package/licenses/rust/serde_json-1.0.149/LICENSE-MIT +23 -0
- package/licenses/rust/serde_urlencoded-0.7.1/LICENSE-APACHE +176 -0
- package/licenses/rust/serde_urlencoded-0.7.1/LICENSE-MIT +25 -0
- package/licenses/rust/shlex-2.0.1/LICENSE-APACHE +13 -0
- package/licenses/rust/shlex-2.0.1/LICENSE-MIT +21 -0
- package/licenses/rust/signal-hook-registry-1.4.8/LICENSE-APACHE +201 -0
- package/licenses/rust/signal-hook-registry-1.4.8/LICENSE-MIT +25 -0
- package/licenses/rust/slab-0.4.12/LICENSE +25 -0
- package/licenses/rust/smallvec-1.15.1/LICENSE-APACHE +201 -0
- package/licenses/rust/smallvec-1.15.1/LICENSE-MIT +25 -0
- package/licenses/rust/socket2-0.6.1/LICENSE-APACHE +201 -0
- package/licenses/rust/socket2-0.6.1/LICENSE-MIT +25 -0
- package/licenses/rust/stable_deref_trait-1.2.1/LICENSE-APACHE +201 -0
- package/licenses/rust/stable_deref_trait-1.2.1/LICENSE-MIT +25 -0
- package/licenses/rust/static_assertions-1.1.0/LICENSE-APACHE +202 -0
- package/licenses/rust/static_assertions-1.1.0/LICENSE-MIT +21 -0
- package/licenses/rust/strsim-0.11.1/LICENSE +23 -0
- package/licenses/rust/strum-0.20.0/LICENSE +21 -0
- package/licenses/rust/strum_macros-0.20.1/LICENSE +21 -0
- package/licenses/rust/subtle-2.6.1/LICENSE +29 -0
- package/licenses/rust/syn-1.0.109/LICENSE-APACHE +201 -0
- package/licenses/rust/syn-1.0.109/LICENSE-MIT +23 -0
- package/licenses/rust/syn-2.0.114/LICENSE-APACHE +176 -0
- package/licenses/rust/syn-2.0.114/LICENSE-MIT +23 -0
- package/licenses/rust/syn-3.0.6/LICENSE-APACHE +176 -0
- package/licenses/rust/syn-3.0.6/LICENSE-MIT +23 -0
- package/licenses/rust/sync_wrapper-1.0.2/LICENSE +177 -0
- package/licenses/rust/synstructure-0.14.0/LICENSE +7 -0
- package/licenses/rust/tempfile-3.24.0/LICENSE-APACHE +201 -0
- package/licenses/rust/tempfile-3.24.0/LICENSE-MIT +25 -0
- package/licenses/rust/thiserror-1.0.69/LICENSE-APACHE +176 -0
- package/licenses/rust/thiserror-1.0.69/LICENSE-MIT +23 -0
- package/licenses/rust/thiserror-2.0.21/LICENSE-APACHE +176 -0
- package/licenses/rust/thiserror-2.0.21/LICENSE-MIT +23 -0
- package/licenses/rust/thiserror-impl-1.0.69/LICENSE-APACHE +176 -0
- package/licenses/rust/thiserror-impl-1.0.69/LICENSE-MIT +23 -0
- package/licenses/rust/thiserror-impl-2.0.21/LICENSE-APACHE +176 -0
- package/licenses/rust/thiserror-impl-2.0.21/LICENSE-MIT +23 -0
- package/licenses/rust/tinystr-0.8.4/LICENSE +46 -0
- package/licenses/rust/tinyvec-1.13.3/LICENSE-APACHE.md +202 -0
- package/licenses/rust/tinyvec-1.13.3/LICENSE-MIT.md +5 -0
- package/licenses/rust/tinyvec-1.13.3/LICENSE-ZLIB.md +11 -0
- package/licenses/rust/tokio-1.49.0/LICENSE +21 -0
- package/licenses/rust/tokio-macros-2.6.0/LICENSE +22 -0
- package/licenses/rust/tokio-rustls-0.26.6/LICENSE-APACHE +201 -0
- package/licenses/rust/tokio-rustls-0.26.6/LICENSE-MIT +25 -0
- package/licenses/rust/tower-0.5.3/LICENSE +25 -0
- package/licenses/rust/tower-http-0.6.11/LICENSE +25 -0
- package/licenses/rust/tower-layer-0.3.3/LICENSE +25 -0
- package/licenses/rust/tower-service-0.3.3/LICENSE +25 -0
- package/licenses/rust/tracing-0.1.44/LICENSE +25 -0
- package/licenses/rust/tracing-core-0.1.36/LICENSE +25 -0
- package/licenses/rust/try-lock-0.2.5/LICENSE +21 -0
- package/licenses/rust/unicode-ident-1.0.22/LICENSE-APACHE +176 -0
- package/licenses/rust/unicode-ident-1.0.22/LICENSE-MIT +23 -0
- package/licenses/rust/unicode-ident-1.0.22/LICENSE-UNICODE +39 -0
- package/licenses/rust/unicode-segmentation-1.12.0/COPYRIGHT +7 -0
- package/licenses/rust/unicode-segmentation-1.12.0/LICENSE-APACHE +201 -0
- package/licenses/rust/unicode-segmentation-1.12.0/LICENSE-MIT +25 -0
- package/licenses/rust/untrusted-0.9.0/LICENSE.txt +13 -0
- package/licenses/rust/url-2.5.8/LICENSE-APACHE +201 -0
- package/licenses/rust/url-2.5.8/LICENSE-MIT +25 -0
- package/licenses/rust/utf8_iter-1.0.4/COPYRIGHT +42 -0
- package/licenses/rust/utf8_iter-1.0.4/LICENSE-APACHE +202 -0
- package/licenses/rust/utf8_iter-1.0.4/LICENSE-MIT +25 -0
- package/licenses/rust/utf8parse-0.2.2/LICENSE-APACHE +176 -0
- package/licenses/rust/utf8parse-0.2.2/LICENSE-MIT +25 -0
- package/licenses/rust/version_check-0.9.5/LICENSE-APACHE +201 -0
- package/licenses/rust/version_check-0.9.5/LICENSE-MIT +19 -0
- package/licenses/rust/want-0.3.2/LICENSE +20 -0
- package/licenses/rust/webpki-roots-1.0.9/LICENSE +61 -0
- package/licenses/rust/writeable-0.6.4/LICENSE +46 -0
- package/licenses/rust/yoke-0.8.3/LICENSE +46 -0
- package/licenses/rust/yoke-derive-0.8.4/LICENSE +46 -0
- package/licenses/rust/zerofrom-0.1.8/LICENSE +46 -0
- package/licenses/rust/zerofrom-derive-0.1.8/LICENSE +46 -0
- package/licenses/rust/zeroize-1.9.1/LICENSE-APACHE +202 -0
- package/licenses/rust/zeroize-1.9.1/LICENSE-MIT +25 -0
- package/licenses/rust/zerotrie-0.2.5/LICENSE +46 -0
- package/licenses/rust/zerovec-0.11.8/LICENSE +46 -0
- package/licenses/rust/zerovec-derive-0.11.6/LICENSE +46 -0
- package/licenses/rust/zmij-1.0.14/LICENSE-MIT +23 -0
- package/package.json +33 -4
- package/runtime/bin/heaplens +0 -0
- package/runtime/bin/hprof-server +0 -0
- package/runtime/browser-ui/ai-actions.cjs +126 -0
- package/runtime/browser-ui/ai.cjs +121 -0
- package/runtime/browser-ui/build/fix-system-prompt.txt +17 -0
- package/runtime/browser-ui/build/index.html +7605 -0
- package/runtime/browser-ui/build/prompts.cjs +188 -0
- package/runtime/browser-ui/build/providers.json +65 -0
- package/runtime/browser-ui/build/system-prompt.txt +41 -0
- package/runtime/browser-ui/consent-actions.cjs +25 -0
- package/runtime/browser-ui/export-actions.cjs +47 -0
- package/runtime/browser-ui/guards.cjs +8 -0
- package/runtime/browser-ui/monitor-actions.cjs +37 -0
- package/runtime/browser-ui/monitor.cjs +73 -0
- package/runtime/browser-ui/object-actions.cjs +60 -0
- package/runtime/browser-ui/progressive-actions.cjs +6 -0
- package/runtime/browser-ui/rpc.cjs +115 -0
- package/runtime/browser-ui/server.cjs +209 -0
- package/runtime/browser-ui/session.cjs +184 -0
- package/runtime/browser-ui/snapshot-actions.cjs +38 -0
- package/runtime/browser-ui/source-actions.cjs +38 -0
- package/runtime/browser-ui/sources.cjs +72 -0
- package/runtime/browser-ui/telemetry-actions.cjs +39 -0
- package/runtime/browser-ui/waste-actions.cjs +20 -0
- package/runtime/package.json +1 -0
- package/runtime/telemetry/contract.cjs +31 -0
- package/runtime/telemetry/contract.json +60 -0
- package/runtime/telemetry/node.cjs +62 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { spawn } = require('node:child_process');
|
|
3
|
+
const { EventEmitter } = require('node:events');
|
|
4
|
+
// Preserve wide integers as decimal strings. Existing renderer actions reject
|
|
5
|
+
// unsafe IDs rather than selecting an adjacent object after JSON rounding.
|
|
6
|
+
function parseExact(text) {
|
|
7
|
+
let out = '', quoted = false, escaped = false;
|
|
8
|
+
for (let i = 0; i < text.length;) {
|
|
9
|
+
const c = text[i];
|
|
10
|
+
if (quoted) {
|
|
11
|
+
out += c;
|
|
12
|
+
i++;
|
|
13
|
+
if (escaped)
|
|
14
|
+
escaped = false;
|
|
15
|
+
else if (c === '\\')
|
|
16
|
+
escaped = true;
|
|
17
|
+
else if (c === '"')
|
|
18
|
+
quoted = false;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (c === '"') {
|
|
22
|
+
quoted = true;
|
|
23
|
+
out += c;
|
|
24
|
+
i++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (c === '-' || /[0-9]/.test(c)) {
|
|
28
|
+
const match = text.slice(i).match(/^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/);
|
|
29
|
+
if (!match)
|
|
30
|
+
throw Error('Invalid JSON');
|
|
31
|
+
const n = match[0];
|
|
32
|
+
out += !/[.eE]/.test(n) && (BigInt(n) > 9007199254740991n || BigInt(n) < -9007199254740991n) ? JSON.stringify(n) : n;
|
|
33
|
+
i += n.length;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
out += c;
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return JSON.parse(out);
|
|
41
|
+
}
|
|
42
|
+
class Rpc extends EventEmitter {
|
|
43
|
+
constructor(executable, legacy = false) {
|
|
44
|
+
super();
|
|
45
|
+
this.pending = new Map();
|
|
46
|
+
this.seq = 0;
|
|
47
|
+
this.buffer = '';
|
|
48
|
+
this.closed = false;
|
|
49
|
+
this.child = spawn(executable, legacy ? ['--legacy'] : [], { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
50
|
+
this.child.stderr.on('data', () => { }); // Never forward raw heap paths/parser bytes.
|
|
51
|
+
this.child.stdin.on('error', () => this.fail());
|
|
52
|
+
this.child.on('error', () => this.fail());
|
|
53
|
+
this.child.on('exit', () => this.fail());
|
|
54
|
+
this.child.stdout.setEncoding('utf8');
|
|
55
|
+
this.child.stdout.on('data', text => {
|
|
56
|
+
if (this.closed)
|
|
57
|
+
return;
|
|
58
|
+
this.buffer += text;
|
|
59
|
+
if (Buffer.byteLength(this.buffer) > 64 * 1024 * 1024)
|
|
60
|
+
return this.fail();
|
|
61
|
+
let end;
|
|
62
|
+
while ((end = this.buffer.indexOf('\n')) !== -1) {
|
|
63
|
+
const line = this.buffer.slice(0, end);
|
|
64
|
+
this.buffer = this.buffer.slice(end + 1);
|
|
65
|
+
try {
|
|
66
|
+
const message = parseExact(line);
|
|
67
|
+
if (message.id !== undefined) {
|
|
68
|
+
const p = this.pending.get(message.id);
|
|
69
|
+
if (!p)
|
|
70
|
+
continue;
|
|
71
|
+
this.pending.delete(message.id);
|
|
72
|
+
clearTimeout(p.timer);
|
|
73
|
+
message.error ? p.reject(Error('Engine request failed. Check syntax or retry.')) : p.resolve(message.result);
|
|
74
|
+
}
|
|
75
|
+
else if (message.method)
|
|
76
|
+
this.emit('notification', message);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return this.fail();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
request(method, params, timeout = 30000) {
|
|
85
|
+
if (this.closed)
|
|
86
|
+
return Promise.reject(Error('Server unavailable. Retry.'));
|
|
87
|
+
const id = ++this.seq;
|
|
88
|
+
const promise = new Promise((resolve, reject) => {
|
|
89
|
+
const timer = setTimeout(() => { this.pending.delete(id); reject(Error('Engine request did not reply. Retry or simplify the query.')); }, timeout);
|
|
90
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
91
|
+
this.child.stdin.write(serialize({ jsonrpc: '2.0', id, method, params }, method === 'compare_snapshots') + '\n');
|
|
92
|
+
});
|
|
93
|
+
return { id, promise };
|
|
94
|
+
}
|
|
95
|
+
call(method, params, timeout) { const r = this.request(method, params, timeout); return r.promise || r; }
|
|
96
|
+
fail() { if (this.closed)
|
|
97
|
+
return; this.closed = true; for (const p of this.pending.values()) {
|
|
98
|
+
clearTimeout(p.timer);
|
|
99
|
+
p.reject(Error('Server unavailable. Retry.'));
|
|
100
|
+
} this.pending.clear(); this.child.kill(); this.emit('unavailable'); }
|
|
101
|
+
close() { this.removeAllListeners(); this.fail(); }
|
|
102
|
+
}
|
|
103
|
+
// Only typed engine numeric fields are emitted as exact unquoted decimals.
|
|
104
|
+
// Ordinary heap/user strings (including digits) remain JSON strings.
|
|
105
|
+
const integerFields = new Set(['object_id', 'total_heap_size', 'reachable_heap_size', 'total_instances', 'total_classes', 'total_arrays', 'total_gc_roots', 'instance_count', 'shallow_size', 'retained_size', 'total_wasted_bytes', 'duplicate_string_wasted_bytes', 'empty_collection_wasted_bytes', 'over_allocated_wasted_bytes', 'boxed_primitive_wasted_bytes']);
|
|
106
|
+
function serialize(value, exact = false, key = '') {
|
|
107
|
+
if (exact && integerFields.has(key) && typeof value === 'string' && /^(0|[1-9]\d*)$/.test(value) && BigInt(value) <= 18446744073709551615n)
|
|
108
|
+
return value;
|
|
109
|
+
if (Array.isArray(value))
|
|
110
|
+
return '[' + value.map(x => serialize(x, exact)).join(',') + ']';
|
|
111
|
+
if (value && typeof value === 'object')
|
|
112
|
+
return '{' + Object.entries(value).filter(([, v]) => v !== undefined).map(([k, v]) => JSON.stringify(k) + ':' + serialize(v, exact, k)).join(',') + '}';
|
|
113
|
+
return JSON.stringify(value);
|
|
114
|
+
}
|
|
115
|
+
module.exports = { Rpc, parseExact, serialize };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const http = require('node:http'), fs = require('node:fs/promises'), sync = require('node:fs'), path = require('node:path'), crypto = require('node:crypto');
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
const { Sources } = require('./sources.cjs'), { Session } = require('./session.cjs');
|
|
5
|
+
const {Telemetry}=require('../telemetry/node.cjs');
|
|
6
|
+
const {platform,architecture}=require('../telemetry/contract.cjs');
|
|
7
|
+
class BrowserHost {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.server = options.server;
|
|
10
|
+
this.telemetry=new Telemetry({context:{host:'browser',version:require('../package.json').version,os:platform(process.platform),arch:architecture(process.arch)},
|
|
11
|
+
level:options.telemetry??'all',disabled:!!process.env.CI || process.env.DO_NOT_TRACK==='1',...(options.telemetrySend?{send:options.telemetrySend}:{})});
|
|
12
|
+
this.legacy = options.legacy;
|
|
13
|
+
this.quickScan = !!options.quickScan;
|
|
14
|
+
this.sourceRoots = options.roots || [];
|
|
15
|
+
this.sources = new Sources(this.sourceRoots);
|
|
16
|
+
this.sessions = new Map();
|
|
17
|
+
this.queue = Promise.resolve();
|
|
18
|
+
this.token = crypto.randomBytes(32).toString('base64url');
|
|
19
|
+
this.stopped = false;
|
|
20
|
+
this.lastSeen = Date.now();
|
|
21
|
+
this.seen = false;
|
|
22
|
+
const assets = options.assets || path.join(__dirname, 'build');
|
|
23
|
+
this.html = sync.readFileSync(path.join(assets, 'index.html'), 'utf8');
|
|
24
|
+
this.providers = JSON.parse(sync.readFileSync(path.join(assets, 'providers.json'), 'utf8'));
|
|
25
|
+
this.prompts = require(path.join(assets, 'prompts.cjs'));
|
|
26
|
+
options.files.forEach(file => { const session = new Session(this, file); this.sessions.set(session.id, session); });
|
|
27
|
+
}
|
|
28
|
+
available() { return [...this.sessions.values()].filter(s => s.state === 'ready' && s.snapshot); }
|
|
29
|
+
changed() { for (const s of this.sessions.values())
|
|
30
|
+
if (s.state !== 'closed')
|
|
31
|
+
s.emit('snapshotsChanged'); }
|
|
32
|
+
slot(work) { const result = this.queue.then(() => this.stopped ? undefined : work()); this.queue = result.catch(() => { }); return result; }
|
|
33
|
+
async listen(port = 0) {
|
|
34
|
+
this.http = http.createServer((req, res) => this.handle(req, res));
|
|
35
|
+
this.http.requestTimeout = 15000;
|
|
36
|
+
this.http.headersTimeout = 10000;
|
|
37
|
+
this.http.maxHeadersCount = 32;
|
|
38
|
+
await new Promise((resolve, reject) => { this.http.once('error', reject); this.http.listen(port, '127.0.0.1', resolve); });
|
|
39
|
+
this.port = this.http.address().port;
|
|
40
|
+
this.origin = 'http://127.0.0.1:' + this.port;
|
|
41
|
+
this.idle = setInterval(() => { if (Date.now() - this.lastSeen > (this.seen ? 90000 : 300000))
|
|
42
|
+
this.shutdown(); }, 5000);
|
|
43
|
+
this.idle.unref();
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
url(id = [...this.sessions.keys()][0]) { return this.origin + '/dump/' + id + '#token=' + this.token; }
|
|
47
|
+
headers(res) { res.setHeader('Cache-Control', 'no-store'); res.setHeader('Referrer-Policy', 'no-referrer'); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('Cross-Origin-Resource-Policy', 'same-origin'); res.setHeader('Content-Security-Policy', "frame-ancestors 'none'; base-uri 'none'; form-action 'none'"); }
|
|
48
|
+
reply(res, status, data) { if (res.destroyed || res.writableEnded)
|
|
49
|
+
return; this.headers(res); res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' }); res.end(JSON.stringify(data)); }
|
|
50
|
+
authenticated(req) { const raw = req.headers['x-heaplens-token']; if (typeof raw !== 'string' || raw.length !== this.token.length)
|
|
51
|
+
return false; return crypto.timingSafeEqual(Buffer.from(raw), Buffer.from(this.token)); }
|
|
52
|
+
async handle(req, res) {
|
|
53
|
+
try {
|
|
54
|
+
if (req.headers.host !== '127.0.0.1:' + this.port)
|
|
55
|
+
return this.reply(res, 403, { error: 'Invalid host' });
|
|
56
|
+
const url = new URL(req.url, this.origin);
|
|
57
|
+
if (url.origin !== this.origin || url.searchParams.has('token'))
|
|
58
|
+
return this.reply(res, 403, { error: 'Invalid request' });
|
|
59
|
+
if (req.method === 'GET' && /^\/dump\/[a-f0-9-]{36}$/.test(url.pathname) && this.sessions.get(url.pathname.split('/').at(-1))?.state !== 'closed' && this.sessions.has(url.pathname.split('/').at(-1))) {
|
|
60
|
+
const nonce = crypto.randomBytes(24).toString('base64');
|
|
61
|
+
this.headers(res);
|
|
62
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
63
|
+
res.end(this.html.replaceAll('__NONCE__', nonce));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!url.pathname.startsWith('/api/') || !this.authenticated(req) || req.headers.origin && req.headers.origin !== this.origin || req.headers['sec-fetch-site'] && req.headers['sec-fetch-site'] !== 'same-origin' && req.headers['sec-fetch-site'] !== 'none' || req.method === 'POST' && req.headers.origin !== this.origin)
|
|
67
|
+
return this.reply(res, 403, { error: 'Unauthorized local request' });
|
|
68
|
+
this.lastSeen = Date.now();
|
|
69
|
+
this.seen = true;
|
|
70
|
+
if (req.method === 'GET' && url.pathname === '/api/catalog')
|
|
71
|
+
return this.reply(res, 200, { dumps: [...this.sessions.values()].filter(s => s.state !== 'closed').map(s => ({ id: s.id, label: s.file.label })) });
|
|
72
|
+
if (req.method === 'GET' && url.pathname === '/api/events') {
|
|
73
|
+
const session = this.sessions.get(url.searchParams.get('session')), after = Number(url.searchParams.get('after'));
|
|
74
|
+
if (!session || session.view !== req.headers['x-heaplens-view'] || !Number.isSafeInteger(after) || after < 0 || after > session.seq)
|
|
75
|
+
return this.reply(res, 400, { error: 'Invalid event cursor or retired page' });
|
|
76
|
+
while (session.events.length && session.events[0].seq <= after)
|
|
77
|
+
session.eventBytes -= session.events.shift().bytes;
|
|
78
|
+
const reset = after > 0 && session.events.length && after < session.events[0].seq - 1;
|
|
79
|
+
return this.reply(res, 200, { reset: !!reset, cursor: session.seq, events: reset ? [] : session.events.map(e => ({ seq: e.seq, message: e.message })) });
|
|
80
|
+
}
|
|
81
|
+
if (req.method === 'POST' && url.pathname === '/api/command') {
|
|
82
|
+
const body = await this.body(req);
|
|
83
|
+
const session = this.sessions.get(body.session);
|
|
84
|
+
if (!session || !body.message || !session.commands.has(body.message.command))
|
|
85
|
+
return this.reply(res, 400, { error: 'Unsupported action' });
|
|
86
|
+
// Do not keep an HTTP request open for an analysis, approval or stream.
|
|
87
|
+
if (body.message.command === 'ready')
|
|
88
|
+
session.attach(req.headers['x-heaplens-view']);
|
|
89
|
+
else if (session.view !== req.headers['x-heaplens-view'])
|
|
90
|
+
return this.reply(res, 403, { error: 'Retired page' });
|
|
91
|
+
const generation = session.generation, view = session.view;
|
|
92
|
+
// Acknowledge before running a side effect: shutdown must not
|
|
93
|
+
// destroy its own HTTP response or strand the launcher's client.
|
|
94
|
+
res.once('finish', () => setImmediate(() => {
|
|
95
|
+
if (this.stopped || session.view !== view)
|
|
96
|
+
return;
|
|
97
|
+
session.dispatch(body.message).catch(() => {
|
|
98
|
+
if (session.state !== 'closed' && session.generation === generation && session.view === view)
|
|
99
|
+
session.emit('localActionStatus', { message: 'Action rejected or failed. Check limits and analysis status.' });
|
|
100
|
+
});
|
|
101
|
+
}));
|
|
102
|
+
this.reply(res, 202, { accepted: true });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.reply(res, 404, { error: 'Not found' });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
this.reply(res, 400, { error: 'Invalid local request' });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async body(req) { if (req.headers['content-type'] !== 'application/json')
|
|
112
|
+
throw Error('Expected JSON'); let bytes = 0; const chunks = []; for await (const chunk of req) {
|
|
113
|
+
bytes += chunk.length;
|
|
114
|
+
if (bytes > 9 * 1024 * 1024)
|
|
115
|
+
throw Error('Request exceeds limit');
|
|
116
|
+
chunks.push(chunk);
|
|
117
|
+
} return JSON.parse(Buffer.concat(chunks).toString('utf8')); }
|
|
118
|
+
async shutdown() {
|
|
119
|
+
if (this.stopped)
|
|
120
|
+
return;
|
|
121
|
+
this.stopped = true;
|
|
122
|
+
this.telemetry.dispose();
|
|
123
|
+
clearInterval(this.idle);
|
|
124
|
+
for (const session of this.sessions.values()) {
|
|
125
|
+
session.emit('browserStopped');
|
|
126
|
+
session.close();
|
|
127
|
+
}
|
|
128
|
+
await new Promise(resolve => { if (!this.http?.listening)
|
|
129
|
+
return resolve(); this.http.close(resolve); this.http.closeAllConnections(); });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function options(argv) {
|
|
133
|
+
const out = { roots: [], files: [], legacy: false, port: 0, browser: true, telemetry:'all' };
|
|
134
|
+
for (let i = 0; i < argv.length; i++) {
|
|
135
|
+
const flag = argv[i];
|
|
136
|
+
if (flag === '--legacy')
|
|
137
|
+
out.legacy = true;
|
|
138
|
+
else if (flag === '--quick-scan')
|
|
139
|
+
out.quickScan = true;
|
|
140
|
+
else if (flag === '--no-browser')
|
|
141
|
+
out.browser = false;
|
|
142
|
+
else if (['--server', '--source-root', '--port', '--dump', '--telemetry'].includes(flag)) {
|
|
143
|
+
const value = argv[++i];
|
|
144
|
+
if (!value)
|
|
145
|
+
throw Error('Missing argument');
|
|
146
|
+
if(flag==='--telemetry') {
|
|
147
|
+
if(!['off','error','all'].includes(value))throw Error('Invalid telemetry choice');
|
|
148
|
+
out.telemetry=value;
|
|
149
|
+
} else if (flag === '--server')
|
|
150
|
+
out.server = value;
|
|
151
|
+
else if (flag === '--source-root')
|
|
152
|
+
out.roots.push(value);
|
|
153
|
+
else if (flag === '--port')
|
|
154
|
+
out.port = Number(value);
|
|
155
|
+
else
|
|
156
|
+
out.files.push(value);
|
|
157
|
+
}
|
|
158
|
+
else
|
|
159
|
+
throw Error('Unknown argument');
|
|
160
|
+
}
|
|
161
|
+
if (!out.server || out.files.length < 1 || out.files.length > 8 || out.roots.length > 16 || !Number.isInteger(out.port) || out.port < 0 || out.port > 65535)
|
|
162
|
+
throw Error('Invalid arguments');
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
async function main(argv) {
|
|
166
|
+
let host;
|
|
167
|
+
try {
|
|
168
|
+
const args = options(argv), files = [];
|
|
169
|
+
for (const input of args.files) {
|
|
170
|
+
const file = await fs.realpath(input), stat = await fs.stat(file);
|
|
171
|
+
if (!stat.isFile() || !stat.size || files.some(f => f.path === file))
|
|
172
|
+
throw Error('Invalid input');
|
|
173
|
+
files.push({ path: file, label: path.basename(file), size: stat.size, mtime: stat.mtimeMs });
|
|
174
|
+
}
|
|
175
|
+
args.server = await fs.realpath(args.server);
|
|
176
|
+
if (!(await fs.stat(args.server)).isFile())
|
|
177
|
+
throw Error('Invalid server');
|
|
178
|
+
args.roots = await Promise.all(args.roots.map(async (root) => { const real = await fs.realpath(root); if (!(await fs.stat(real)).isDirectory())
|
|
179
|
+
throw Error('Invalid source root'); return real; }));
|
|
180
|
+
host = await new BrowserHost({ ...args, files }).listen(args.port);
|
|
181
|
+
const launch = host.url();
|
|
182
|
+
process.stdout.write('HeapLens local browser (keep this terminal running):\n' + launch + '\nThis launch URL is a private session capability. Do not share it. Ctrl+C stops owned servers.\n');
|
|
183
|
+
if (args.browser) {
|
|
184
|
+
const launcher = process.platform === 'darwin' ? ['open', [launch]] : process.platform === 'win32' ? ['rundll32', ['url.dll,FileProtocolHandler', launch]] : ['xdg-open', [launch]];
|
|
185
|
+
const browser = spawn(launcher[0], launcher[1], { stdio: 'ignore', windowsHide: true });
|
|
186
|
+
browser.on('error', () => process.stderr.write('Could not open a browser. Paste the private launch URL above.\n'));
|
|
187
|
+
}
|
|
188
|
+
for (const signal of ['SIGINT', 'SIGTERM'])
|
|
189
|
+
process.once(signal, () => host.shutdown());
|
|
190
|
+
// Retire the service if its foreground CLI parent exits unexpectedly.
|
|
191
|
+
const parent = process.ppid;
|
|
192
|
+
const check = setInterval(() => { try {
|
|
193
|
+
process.kill(parent, 0);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
clearInterval(check);
|
|
197
|
+
host.shutdown();
|
|
198
|
+
} }, 3000);
|
|
199
|
+
check.unref();
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
await host?.shutdown();
|
|
203
|
+
process.stderr.write('Cannot start local browser. Check Node runtime, built browser assets, server binary, dump/source paths and port.\n');
|
|
204
|
+
process.exitCode = 3;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (require.main === module)
|
|
208
|
+
main(process.argv.slice(2));
|
|
209
|
+
module.exports = { BrowserHost, options, main };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const crypto = require('node:crypto');
|
|
3
|
+
const { AsyncLocalStorage } = require('node:async_hooks');
|
|
4
|
+
const { Rpc } = require('./rpc.cjs');
|
|
5
|
+
const { requestId, objectId } = require('./guards.cjs');
|
|
6
|
+
const capabilities = ["object-actions", "consent-actions", "source-actions", "ai-actions", "snapshot-actions", "export-actions", "monitor-actions", "telemetry-actions", "progressive-actions", "waste-actions"].map(name => require('./' + name + '.cjs'));
|
|
7
|
+
const rename = { summary: 'summary', top_objects: 'topObjects', top_layers: 'topLayers', class_histogram: 'classHistogram', leak_suspects: 'leakSuspects', object_leak_suspects: 'objectLeakSuspects', waste_analysis: 'wasteAnalysis', storage: 'storage' };
|
|
8
|
+
// Immutable compact snapshots, not a second heap graph or comparator.
|
|
9
|
+
function snapshot(raw) {
|
|
10
|
+
const summary = Object.fromEntries(['total_heap_size', 'reachable_heap_size', 'total_instances', 'total_classes', 'total_arrays', 'total_gc_roots'].map(k => [k, raw.summary[k]]));
|
|
11
|
+
const waste = raw.waste_analysis ? Object.fromEntries(['total_wasted_bytes', 'waste_percentage', 'duplicate_string_wasted_bytes', 'empty_collection_wasted_bytes', 'over_allocated_wasted_bytes', 'boxed_primitive_wasted_bytes'].map(k => [k, raw.waste_analysis[k]])) : null;
|
|
12
|
+
const value = { summary, class_histogram: raw.class_histogram, leak_suspects: raw.leak_suspects, waste_analysis: waste };
|
|
13
|
+
if (Buffer.byteLength(JSON.stringify(value)) > 8 * 1024 * 1024)
|
|
14
|
+
throw Error('Comparison metadata exceeds 8 MiB');
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
class Session {
|
|
18
|
+
constructor(host, file) {
|
|
19
|
+
this.host = host;
|
|
20
|
+
this.file = file;
|
|
21
|
+
this.id = crypto.randomUUID();
|
|
22
|
+
this.generation = 0;
|
|
23
|
+
this.events = [];
|
|
24
|
+
this.seq = 0;
|
|
25
|
+
this.eventBytes = 0;
|
|
26
|
+
this.state = 'idle';
|
|
27
|
+
this.raw = null;
|
|
28
|
+
this.mode = host.quickScan ? 'shallow' : 'full';
|
|
29
|
+
this.history = [];
|
|
30
|
+
this.config = null;
|
|
31
|
+
this.chatGrant = null;
|
|
32
|
+
this.pendingConsent = null;
|
|
33
|
+
this.activeAi = null;
|
|
34
|
+
this.actionContext = new AsyncLocalStorage();
|
|
35
|
+
// New host capabilities extend this registry instead of changing analysis classes.
|
|
36
|
+
this.commands = new Map(Object.entries({ ready: m => this.ready(), retryAnalysis: m => this.analyze(), cancelAnalysis: m => this.cancel(), closeDump: m => this.close(), shutdown: m => this.host.shutdown() }));
|
|
37
|
+
for (const capability of capabilities)
|
|
38
|
+
capability.install(this);
|
|
39
|
+
}
|
|
40
|
+
purge() { this.events = []; this.eventBytes = 0; }
|
|
41
|
+
attach(view) { if (typeof view !== 'string' || !/^[a-f0-9-]{36}$/.test(view))
|
|
42
|
+
throw Error('Invalid page session'); if (this.view !== view) {
|
|
43
|
+
this.view = view;
|
|
44
|
+
this.stopAi();
|
|
45
|
+
this.monitor?.close();
|
|
46
|
+
this.monitor = null;
|
|
47
|
+
this.chatGrant = null;
|
|
48
|
+
this.purge();
|
|
49
|
+
} }
|
|
50
|
+
emit(command, data = {}) {
|
|
51
|
+
const action = this.actionContext.getStore();
|
|
52
|
+
if (action && (action.view !== this.view || action.generation !== this.generation))
|
|
53
|
+
return;
|
|
54
|
+
const message = { command, ...data };
|
|
55
|
+
try { require('./telemetry-actions.cjs').observe(this,command,data); } catch { /* Diagnostics cannot fail UI delivery. */ }
|
|
56
|
+
const bytes = Buffer.byteLength(JSON.stringify(message));
|
|
57
|
+
if (bytes > 64 * 1024 * 1024)
|
|
58
|
+
throw Error('UI response exceeds 64 MiB');
|
|
59
|
+
this.events.push({ seq: ++this.seq, message, bytes });
|
|
60
|
+
this.eventBytes += bytes;
|
|
61
|
+
while (this.events.length > 256 || this.eventBytes > 64 * 1024 * 1024) {
|
|
62
|
+
this.eventBytes -= this.events.shift().bytes;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async dispatch(m) {
|
|
66
|
+
if (!m || typeof m !== 'object' || Array.isArray(m) || !this.commands.has(m.command) || this.state === 'closed' && !['ready', 'shutdown'].includes(m.command))
|
|
67
|
+
throw Error('Unsupported action');
|
|
68
|
+
const action = this.commands.get(m.command);
|
|
69
|
+
if (['ready', 'retryAnalysis', 'quickScan', 'analyzeRetained', 'cancelAnalysis', 'closeDump', 'shutdown'].includes(m.command))
|
|
70
|
+
await this.actionContext.run(undefined, () => action(m));
|
|
71
|
+
else
|
|
72
|
+
await this.actionContext.run({ view: this.view, generation: this.generation }, () => action(m));
|
|
73
|
+
}
|
|
74
|
+
status(message) { this.emit('browserStatus', { message }); }
|
|
75
|
+
ready() {
|
|
76
|
+
if (this.raw) {
|
|
77
|
+
if(this.mode === 'shallow') this.emit('shallowScanComplete', {summary:this.raw.summary,classHistogram:this.raw.class_histogram,readiness:this.raw.readiness});
|
|
78
|
+
else this.emit('analysisComplete', { ...Object.fromEntries(Object.entries(rename).map(([a, b]) => [b, this.raw[a]])), displayName: this.file.label });
|
|
79
|
+
this.status('READY | server PID ' + this.rpc?.child.pid);
|
|
80
|
+
this.emit('aiHistory', { messages: this.history });
|
|
81
|
+
}
|
|
82
|
+
else if (this.state === 'idle')
|
|
83
|
+
this.analyze();
|
|
84
|
+
else
|
|
85
|
+
this.status(this.state === 'closed' ? 'Dump closed. Start a new CLI session to reopen.' : this.state.toUpperCase() + ' | server PID ' + (this.rpc?.child.pid || 'pending'));
|
|
86
|
+
}
|
|
87
|
+
async analyze(mode = this.mode) {
|
|
88
|
+
if (['loading', 'queued'].includes(this.state))
|
|
89
|
+
return;
|
|
90
|
+
const generation = ++this.generation;
|
|
91
|
+
this.mode = mode;
|
|
92
|
+
const shallow = mode === 'shallow';
|
|
93
|
+
this.stopAi();
|
|
94
|
+
this.chatGrant = null;
|
|
95
|
+
this.history = [];
|
|
96
|
+
this.monitor?.close();
|
|
97
|
+
this.raw = null;
|
|
98
|
+
this.snapshot = null;
|
|
99
|
+
this.rpc?.close();
|
|
100
|
+
this.purge();
|
|
101
|
+
this.state = 'queued';
|
|
102
|
+
this.host.changed();
|
|
103
|
+
this.emit('analysisStarting',{analysisScope:mode});
|
|
104
|
+
this.emit('analysisProgress', { stage: 'loading', phase: 1, totalPhases: 4, fileMetadata: { file_size: this.file.size } });
|
|
105
|
+
this.status('Waiting for analysis slot. Other dumps remain available.');
|
|
106
|
+
await this.host.slot(async () => {
|
|
107
|
+
if (generation !== this.generation || this.state === 'closed')
|
|
108
|
+
return;
|
|
109
|
+
this.state = 'loading';
|
|
110
|
+
this.rpc = new Rpc(this.host.server, this.host.legacy);
|
|
111
|
+
const rpc = this.rpc;
|
|
112
|
+
this.status('ANALYZING | server PID ' + rpc.child.pid);
|
|
113
|
+
rpc.on('unavailable', () => { if (generation !== this.generation)
|
|
114
|
+
return; this.state = 'failed'; this.raw = null; this.snapshot = null; this.stopAi(); this.host.changed(); this.emit('serverCrashed', { message: 'Analysis server unavailable. Retry starts a new server.' }); this.status('SERVER UNAVAILABLE | Retry'); this.finishAnalysis?.(); });
|
|
115
|
+
let finish;
|
|
116
|
+
const completed = new Promise(resolve => { finish = resolve; });
|
|
117
|
+
this.finishAnalysis = finish;
|
|
118
|
+
rpc.on('notification', message => {
|
|
119
|
+
if (generation !== this.generation || this.state !== 'loading' || message.params?.request_id !== this.analysisId)
|
|
120
|
+
return;
|
|
121
|
+
const p = message.params;
|
|
122
|
+
if (message.method === (shallow ? 'heap_scan_progress' : 'heap_analysis_progress'))
|
|
123
|
+
this.emit('analysisProgress', { stage: p.stage, phase: p.phase, totalPhases: p.total_phases, summary: p.summary, snapshot: p.snapshot, fileMetadata: p.file_metadata });
|
|
124
|
+
if (message.method === (shallow ? 'heap_scan_complete' : 'heap_analysis_complete')) {
|
|
125
|
+
if (p.status === 'completed') {
|
|
126
|
+
this.raw = p;
|
|
127
|
+
this.state = shallow ? 'shallow-ready' : 'ready';
|
|
128
|
+
try {
|
|
129
|
+
this.snapshot = shallow ? null : snapshot(p);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
this.snapshot = null;
|
|
133
|
+
this.status('READY. Comparison metadata exceeds its limit.');
|
|
134
|
+
}
|
|
135
|
+
this.ready();
|
|
136
|
+
this.host.changed();
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this.state = p.status === 'cancelled' ? 'cancelled' : 'failed';
|
|
140
|
+
this.raw = null;
|
|
141
|
+
this.snapshot = null;
|
|
142
|
+
this.emit(p.status === 'cancelled' ? 'analysisCancelled' : 'serverCrashed', { message: 'Analysis failed or was cancelled. Retry.' });
|
|
143
|
+
this.status(this.state.toUpperCase() + ' | Retry');
|
|
144
|
+
}
|
|
145
|
+
finish();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
try {
|
|
149
|
+
const started = rpc.request(shallow ? 'scan_heap' : 'analyze_heap', { path: this.file.path }, 30000);
|
|
150
|
+
this.analysisId = started.id;
|
|
151
|
+
await started.promise;
|
|
152
|
+
await completed;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
if (generation === this.generation) {
|
|
156
|
+
this.state = 'failed';
|
|
157
|
+
this.emit('serverCrashed', { message: 'Server did not acknowledge analysis. Retry.' });
|
|
158
|
+
rpc.close();
|
|
159
|
+
}
|
|
160
|
+
finish();
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
if (this.finishAnalysis === finish)
|
|
164
|
+
this.finishAnalysis = null;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
async cancel() {
|
|
169
|
+
if (this.state === 'queued') {
|
|
170
|
+
this.generation++;
|
|
171
|
+
this.state = 'cancelled';
|
|
172
|
+
this.emit('analysisCancelled');
|
|
173
|
+
this.status('CANCELLED | Retry');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (this.state === 'loading')
|
|
177
|
+
await this.rpc.call('cancel_analysis', { path: this.file.path, analysis_request_id: this.analysisId }).catch(() => { });
|
|
178
|
+
}
|
|
179
|
+
async engine(method, params) { if (this.state !== 'ready' || !this.rpc)
|
|
180
|
+
throw Error('Wait for analysis or Retry.'); const gen = this.generation; const result = await this.rpc.call(method, { ...params, path: this.file.path }); if (gen !== this.generation || this.state !== 'ready')
|
|
181
|
+
throw Error('Analysis changed. Repeat the action.'); return result; }
|
|
182
|
+
close() { this.generation++; this.state = 'closed'; this.stopAi(); this.config = null; this.history = []; this.raw = null; this.snapshot = null; this.rpc?.close(); this.monitor?.close(); this.finishAnalysis?.(); this.purge(); this.host.changed(); this.emit('analysisCancelled'); this.status('Dump closed; server stopped. Other dumps are unaffected.'); }
|
|
183
|
+
}
|
|
184
|
+
module.exports = { Session, snapshot, requestId, objectId };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { requestId } = require('./guards.cjs');
|
|
3
|
+
const actions = {
|
|
4
|
+
list(all) { const rows = this.host.available().filter(s => all || s !== this); this.emit(all ? 'allAnalyzedFiles' : 'analyzedFiles', { files: rows.map(s => s.id), labels: Object.fromEntries(rows.map(s => [s.id, s.file.label])) }); },
|
|
5
|
+
async compare(m) {
|
|
6
|
+
const id = requestId(m);
|
|
7
|
+
try {
|
|
8
|
+
const baseline = this.host.sessions.get(m.baselinePath);
|
|
9
|
+
if (!baseline?.snapshot || !this.snapshot || baseline === this)
|
|
10
|
+
throw Error();
|
|
11
|
+
const a = baseline.snapshot, b = this.snapshot;
|
|
12
|
+
const result = await this.engine('compare_snapshots', { baseline: a, current: b, baseline_label: baseline.file.label, current_label: this.file.label });
|
|
13
|
+
if (a !== baseline.snapshot || b !== this.snapshot)
|
|
14
|
+
throw Error();
|
|
15
|
+
this.emit('compareResult', { requestId: id, result, sizeModelsDiffer: JSON.stringify(baseline.raw.summary.size_model) !== JSON.stringify(this.raw.summary.size_model) });
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
this.emit('compareError', { requestId: id, error: 'Choose two currently analyzed dumps with comparison metadata below 8 MiB.' });
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
timeline(m) {
|
|
22
|
+
const id = requestId(m);
|
|
23
|
+
try {
|
|
24
|
+
if (!Array.isArray(m.paths) || m.paths.length < 2 || m.paths.length > 8 || new Set(m.paths).size !== m.paths.length)
|
|
25
|
+
throw Error();
|
|
26
|
+
const selected = m.paths.map(id => { const s = this.host.sessions.get(id); if (!s?.snapshot)
|
|
27
|
+
throw Error(); return s; }).sort((a, b) => a.file.mtime - b.file.mtime);
|
|
28
|
+
const tracked = new Set(selected.flatMap(s => s.snapshot.class_histogram.slice(0, 10).map(x => x.class_name)));
|
|
29
|
+
this.emit('timelineDataResponse', { requestId: id, sizeModelsDiffer: new Set(selected.map(s => JSON.stringify(s.raw.summary.size_model))).size > 1, result: { snapshots: selected.map(s => ({ path: s.file.label, timestamp: s.file.mtime / 1000, summary: s.snapshot.summary, size_model: s.raw.summary.size_model, top_classes: s.snapshot.class_histogram.filter(x => tracked.has(x.class_name)) })) } });
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
this.emit('timelineError', { requestId: id, error: 'Select 2–8 analyzed dumps. Time order is file modification time, not necessarily capture time.' });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
module.exports.install = session => { for (const [name, action] of Object.entries(actions))
|
|
37
|
+
session[name] = action.bind(session); for (const [command, handler] of Object.entries({ listAnalyzedFiles: m => session.list(false), listAllAnalyzedFiles: m => session.list(true), compareHeaps: m => session.compare(m), getTimelineData: m => session.timeline(m) }))
|
|
38
|
+
session.commands.set(command, handler); };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { requestId } = require('./guards.cjs');
|
|
3
|
+
const actions = {
|
|
4
|
+
async selectSource(name, signal) {
|
|
5
|
+
const matches = await this.host.sources.find(name);
|
|
6
|
+
if (!matches.length)
|
|
7
|
+
throw Error('No matching Java file under --source-root. Add the source folder when launching.');
|
|
8
|
+
if (matches.length === 1)
|
|
9
|
+
return matches[0];
|
|
10
|
+
const selected = await this.consent('Choose source', 'Several files match. Select a local file; no network request is made.', matches.map((f, i) => [f.label + ' (root ' + f.rootIndex + ')', String(i)]).concat([['Cancel', null]]), signal);
|
|
11
|
+
return selected === null ? null : matches[Number(selected)];
|
|
12
|
+
},
|
|
13
|
+
async source(m) {
|
|
14
|
+
const id = requestId(m), gen = this.generation;
|
|
15
|
+
try {
|
|
16
|
+
if (this.state !== 'ready')
|
|
17
|
+
throw Error();
|
|
18
|
+
const file = await this.selectSource(m.className);
|
|
19
|
+
if (gen !== this.generation)
|
|
20
|
+
return;
|
|
21
|
+
if (!file) {
|
|
22
|
+
this.emit('sourceNavigationResult', { requestId: id, className: m.className, status: 'cancelled' });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const text = await this.host.sources.read(file);
|
|
26
|
+
if (gen !== this.generation)
|
|
27
|
+
return;
|
|
28
|
+
this.emit('browserSource', { label: file.label, text });
|
|
29
|
+
this.emit('sourceNavigationResult', { requestId: id, className: m.className, status: 'opened' });
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
this.emit('sourceNavigationResult', { requestId: id, className: m.className, status: 'not-found' });
|
|
33
|
+
this.emit('localActionStatus', { message: 'Source unavailable. Supply --source-root. Dependency-JAR lookup/decompilation is not bundled.' });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
module.exports.install = session => { for (const [name, action] of Object.entries(actions))
|
|
38
|
+
session[name] = action.bind(session); session.commands.set('openProjectSource', m => session.source(m)); };
|