From 9a73dcda6d8b7db95e2aac9dc5d1db0a1197798c Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 3 Mar 2026 14:44:18 +0100 Subject: [PATCH 1/7] refactor: use crate from deno repo --- .gitignore | 4 +- Cargo.lock | 801 ++++---------- Cargo.toml | 3 - LICENCE | 2 +- auth_tokens.ts | 2 +- auth_tokens_test.ts | 2 +- cache.ts | 2 +- cache_test.ts | 2 +- deno.json | 2 +- deno_dir.ts | 7 +- deno_dir_test.ts | 2 +- deps_test.ts | 2 +- disk_cache.ts | 7 +- disk_cache_test.ts | 10 +- file_fetcher.ts | 2 +- file_fetcher_test.ts | 2 +- http_cache.ts | 11 +- mod.ts | 2 +- rs_lib/Cargo.toml | 37 +- rs_lib/lib.rs | 251 +++++ rs_lib/src/cache.rs | 344 ------ rs_lib/src/common.rs | 66 -- rs_lib/src/deno_dir.rs | 62 -- rs_lib/src/file_fetcher/auth_tokens.rs | 361 ------- rs_lib/src/file_fetcher/http_util.rs | 154 --- rs_lib/src/file_fetcher/mod.rs | 1366 ----------------------- rs_lib/src/global/cache_file.rs | 133 --- rs_lib/src/global/mod.rs | 258 ----- rs_lib/src/lib.rs | 290 ----- rs_lib/src/local.rs | 1371 ------------------------ rs_lib/src/memory.rs | 156 --- rs_lib/src/npm.rs | 315 ------ rs_lib/src/sync.rs | 29 - rs_lib/tests/file_fetcher_test.rs | 176 --- rs_lib/tests/integration_test.rs | 794 -------------- rust-toolchain.toml | 2 +- test.ts | 2 +- util.ts | 2 +- 38 files changed, 506 insertions(+), 6528 deletions(-) create mode 100644 rs_lib/lib.rs delete mode 100644 rs_lib/src/cache.rs delete mode 100644 rs_lib/src/common.rs delete mode 100644 rs_lib/src/deno_dir.rs delete mode 100644 rs_lib/src/file_fetcher/auth_tokens.rs delete mode 100644 rs_lib/src/file_fetcher/http_util.rs delete mode 100644 rs_lib/src/file_fetcher/mod.rs delete mode 100644 rs_lib/src/global/cache_file.rs delete mode 100644 rs_lib/src/global/mod.rs delete mode 100644 rs_lib/src/lib.rs delete mode 100644 rs_lib/src/local.rs delete mode 100644 rs_lib/src/memory.rs delete mode 100644 rs_lib/src/npm.rs delete mode 100644 rs_lib/src/sync.rs delete mode 100644 rs_lib/tests/file_fetcher_test.rs delete mode 100644 rs_lib/tests/integration_test.rs diff --git a/.gitignore b/.gitignore index 5e063bb..18b79e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ .vscode /target -lib/snippets -lib/deno_cache_dir.generated.js -lib/deno_cache_dir.generated.d.ts +lib/ diff --git a/Cargo.lock b/Cargo.lock index aadc958..31f9103 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,26 +2,11 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -30,24 +15,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.73" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base32" @@ -63,15 +33,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -94,15 +58,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytes" -version = "1.9.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cache_control" @@ -110,26 +74,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2a5fb3207c12b5d208ebc145f967fea5cac41a021c37417ccc31ba40f39ee" -[[package]] -name = "cc" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" -dependencies = [ - "shlex", -] - [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "num-traits", ] @@ -146,18 +101,18 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -165,13 +120,15 @@ dependencies = [ [[package]] name = "data-url" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] name = "deno_cache_dir" version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b557ab2ef7973b4ba4c561631a46ce75fa52734d1845100d0b9bc1b6bc535ec" dependencies = [ "async-trait", "base32", @@ -179,35 +136,54 @@ dependencies = [ "boxed_error", "cache_control", "chrono", - "console_error_panic_hook", "data-url", "deno_error", "deno_media_type", "deno_path_util", "http", "indexmap", + "log", + "once_cell", + "parking_lot", + "serde", + "serde_json", + "sha2", + "sys_traits", + "thiserror", + "url", +] + +[[package]] +name = "deno_cache_dir_wasm" +version = "0.0.0" +dependencies = [ + "base32", + "boxed_error", + "console_error_panic_hook", + "deno_cache_dir", + "deno_error", + "deno_media_type", + "deno_path_util", + "indexmap", "js-sys", "log", "once_cell", "parking_lot", - "pretty_assertions", "serde", "serde-wasm-bindgen", "serde_json", "sha2", "sys_traits", - "tempfile", "thiserror", - "tokio", "url", "wasm-bindgen", ] [[package]] name = "deno_error" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde60bd153886964234c5012d3d9caf788287f28d81fb24a884436904101ef10" +checksum = "3007d3f1ea92ea503324ae15883aac0c2de2b8cf6fead62203ff6a67161007ab" dependencies = [ "deno_error_macro", "libc", @@ -216,9 +192,9 @@ dependencies = [ [[package]] name = "deno_error_macro" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409f265785bd946d3006756955aaf40b0e4deb25752eae6a990afe54a31cfd83" +checksum = "9b565e60a9685cdf312c888665b5f8647ac692a7da7e058a5e2268a466da8eaf" dependencies = [ "proc-macro2", "quote", @@ -238,9 +214,9 @@ dependencies = [ [[package]] name = "deno_path_util" -version = "0.6.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07f0a6c02723e125a14caacfe4a44a4972364a243a6dfed42b4b16128c76bc34" +checksum = "78c7e98943f0d068928906db0c7bde89de684fa32c6a8018caacc4cee2cdd72b" dependencies = [ "deno_error", "percent-encoding", @@ -249,12 +225,6 @@ dependencies = [ "url", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.10.7" @@ -278,48 +248,15 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fastrand" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" - -[[package]] -name = "fnv" -version = "1.0.7" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -334,57 +271,40 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "http" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -393,104 +313,66 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -499,9 +381,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -509,88 +391,64 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", "serde", + "serde_core", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ + "once_cell", "wasm-bindgen", ] -[[package]] -name = "junction" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72bbdfd737a243da3dfc1f99ee8d6e166480f17ab4ac84d7c34aacd73fc7bd16" -dependencies = [ - "scopeguard", - "windows-sys 0.52.0", -] - [[package]] name = "libc" -version = "0.2.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" - -[[package]] -name = "linux-raw-sys" -version = "0.4.5" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.19" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "miniz_oxide" -version = "0.7.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "num-traits" @@ -601,26 +459,17 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" -version = "1.18.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -628,90 +477,64 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.1", + "windows-link", ] [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] -name = "pin-project-lite" -version = "0.2.15" +name = "potential_utf" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" - -[[package]] -name = "pretty_assertions" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ - "diff", - "yansi", + "zerovec", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustix" -version = "0.38.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" -dependencies = [ - "bitflags 2.3.3", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "ryu" -version = "1.0.15" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "scopeguard" @@ -721,10 +544,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -739,11 +563,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -752,50 +585,46 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "indexmap", "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -804,9 +633,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -815,18 +644,13 @@ dependencies = [ [[package]] name = "sys_traits" -version = "0.1.17" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f74a2c95f72e36fa6bd04a40d15623a9904bab1cc2fa6c6135b09d774a65088" +checksum = "5410f31d223892c1ce7a098da845c99d023b4c7f18632bc8f09e60dfae3cbb75" dependencies = [ - "getrandom", "js-sys", - "junction", - "libc", - "parking_lot", "sys_traits_macros", "wasm-bindgen", - "windows-sys 0.59.0", ] [[package]] @@ -840,33 +664,20 @@ dependencies = [ "syn", ] -[[package]] -name = "tempfile" -version = "3.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -875,66 +686,39 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", ] -[[package]] -name = "tokio" -version = "1.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" -dependencies = [ - "backtrace", - "pin-project-lite", - "tokio-macros", -] - -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "typenum" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -943,46 +727,28 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" -dependencies = [ - "bumpalo", - "log", "once_cell", - "proc-macro2", - "quote", - "syn", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -990,196 +756,44 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.1", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "unicode-ident", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "yansi" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -1187,9 +801,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -1199,18 +813,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", @@ -1218,11 +832,22 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -1231,11 +856,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 167e993..0048ce8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,6 @@ resolver = "2" members = ["rs_lib"] -[workspace.dependencies] -sys_traits = "0.1.16" - [profile.release] codegen-units = 1 incremental = true diff --git a/LICENCE b/LICENCE index 2429503..262238f 100644 --- a/LICENCE +++ b/LICENCE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018-2025 the Deno authors +Copyright (c) 2018-2026 the Deno authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/auth_tokens.ts b/auth_tokens.ts index 6a4293e..44b33af 100644 --- a/auth_tokens.ts +++ b/auth_tokens.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. interface BearerAuthToken { type: "bearer"; diff --git a/auth_tokens_test.ts b/auth_tokens_test.ts index 1fb2b8d..e41530c 100644 --- a/auth_tokens_test.ts +++ b/auth_tokens_test.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import { AuthTokens } from "./auth_tokens.ts"; import { assertEquals } from "@std/assert"; diff --git a/cache.ts b/cache.ts index 9fe4769..35657ba 100644 --- a/cache.ts +++ b/cache.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import type { LoadResponse } from "@deno/graph"; import type { CacheSetting, FileFetcher } from "./file_fetcher.ts"; diff --git a/cache_test.ts b/cache_test.ts index 0b337bb..8a3c276 100644 --- a/cache_test.ts +++ b/cache_test.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import { FetchCacher } from "./cache.ts"; import { DenoDir } from "./deno_dir.ts"; diff --git a/deno.json b/deno.json index 96e01d5..df62a41 100644 --- a/deno.json +++ b/deno.json @@ -4,7 +4,7 @@ "tasks": { "test": "deno test --allow-read --allow-write --allow-net --allow-env", "build": "deno task wasmbuild", - "wasmbuild": "deno run -A jsr:@deno/wasmbuild@0.16.0 --sync --no-default-features --features wasm" + "wasmbuild": "deno run -A jsr:@deno/wasmbuild@0.21.1" }, "lint": { "rules": { diff --git a/deno_dir.ts b/deno_dir.ts index a893d66..55891ca 100644 --- a/deno_dir.ts +++ b/deno_dir.ts @@ -1,10 +1,10 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import { isAbsolute, join, resolve } from "@std/path"; import { DiskCache } from "./disk_cache.ts"; import { HttpCache } from "./http_cache.ts"; import { assert } from "./util.ts"; -import { instantiate } from "./lib/deno_cache_dir.generated.js"; +import { resolve_deno_dir } from "./lib/deno_cache_dir_wasm.js"; export class DenoDir { readonly root: string; @@ -42,8 +42,7 @@ export class DenoDir { if (root) { return resolvePathOrUrl(root); } else { - const instance = instantiate(); - return instance.resolve_deno_dir(); + return resolve_deno_dir(); } } } diff --git a/deno_dir_test.ts b/deno_dir_test.ts index 1d13c51..f00e47e 100644 --- a/deno_dir_test.ts +++ b/deno_dir_test.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import { assertEquals, assertThrows } from "@std/assert"; import { DenoDir } from "./deno_dir.ts"; diff --git a/deps_test.ts b/deps_test.ts index b280bae..550dc91 100644 --- a/deps_test.ts +++ b/deps_test.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. export { assertEquals, assertRejects } from "@std/assert"; export { createGraph } from "@deno/graph"; diff --git a/disk_cache.ts b/disk_cache.ts index e969e5e..ace6425 100644 --- a/disk_cache.ts +++ b/disk_cache.ts @@ -1,10 +1,10 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import { ensureDir } from "@std/fs/ensure-dir"; import { dirname, isAbsolute, join } from "@std/path"; import { readAll, writeAll } from "@std/io"; import { assert, CACHE_PERM } from "./util.ts"; -import { instantiate } from "./lib/deno_cache_dir.generated.js"; +import { url_to_filename } from "./lib/deno_cache_dir_wasm.js"; export class DiskCache { location: string; @@ -35,8 +35,7 @@ export class DiskCache { file.close(); } - static async getCacheFilename(url: URL): Promise { - const { url_to_filename } = await instantiate(); + static getCacheFilename(url: URL): string { return url_to_filename(url.toString()); } } diff --git a/disk_cache_test.ts b/disk_cache_test.ts index 3d4ea94..73275ad 100644 --- a/disk_cache_test.ts +++ b/disk_cache_test.ts @@ -1,6 +1,6 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. -import { assertEquals, assertRejects } from "@std/assert"; +import { assertEquals, assertThrows } from "@std/assert"; import { DiskCache } from "./disk_cache.ts"; Deno.test({ @@ -28,15 +28,15 @@ Deno.test({ for (const [fixture, expected] of testCases) { if (expected instanceof Error) { - await assertRejects( - async () => await DiskCache.getCacheFilename(new URL(fixture)), + await assertThrows( + () => DiskCache.getCacheFilename(new URL(fixture)), Error, expected.message, ); continue; } else { assertEquals( - await DiskCache.getCacheFilename(new URL(fixture)), + DiskCache.getCacheFilename(new URL(fixture)), expected, ); } diff --git a/file_fetcher.ts b/file_fetcher.ts index 7a51758..2792ec1 100644 --- a/file_fetcher.ts +++ b/file_fetcher.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import { AuthTokens } from "./auth_tokens.ts"; import { fromFileUrl } from "@std/path"; diff --git a/file_fetcher_test.ts b/file_fetcher_test.ts index 2b8effa..dc01254 100644 --- a/file_fetcher_test.ts +++ b/file_fetcher_test.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import { DenoDir } from "./deno_dir.ts"; import { assertRejects, createGraph } from "./deps_test.ts"; diff --git a/http_cache.ts b/http_cache.ts index bb9ac56..c77fa36 100644 --- a/http_cache.ts +++ b/http_cache.ts @@ -1,12 +1,11 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import { isAbsolute } from "@std/path"; import { assert } from "./util.ts"; import { - type GlobalHttpCache, - instantiate, - type LocalHttpCache, -} from "./lib/deno_cache_dir.generated.js"; + GlobalHttpCache, + LocalHttpCache, +} from "./lib/deno_cache_dir_wasm.js"; export interface HttpCacheCreateOptions { root: string; @@ -47,8 +46,6 @@ export class HttpCache implements Disposable { "Vendor root must be an absolute path.", ); } - const { GlobalHttpCache, LocalHttpCache } = await instantiate(); - let cache: LocalHttpCache | GlobalHttpCache; if (options.vendorRoot != null) { cache = LocalHttpCache.new( diff --git a/mod.ts b/mod.ts index d7f2ea0..3fddf30 100644 --- a/mod.ts +++ b/mod.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. /** * A module which provides a TypeScript implementation of the Deno CLI's cache diff --git a/rs_lib/Cargo.toml b/rs_lib/Cargo.toml index 6f0e01e..f1ade14 100644 --- a/rs_lib/Cargo.toml +++ b/rs_lib/Cargo.toml @@ -1,34 +1,21 @@ [package] -name = "deno_cache_dir" -version = "0.27.0" +name = "deno_cache_dir_wasm" +version = "0.0.0" edition = "2024" license = "MIT" -description = "Cache directory logic used in Deno" -repository = "https://github.com/denoland/deno_cache_dir" [lib] crate-type = ["cdylib", "lib"] - -[features] -default = ["file_fetcher"] -file_fetcher = ["async-trait", "base64", "cache_control", "chrono", "data-url", "http"] -wasm = ["console_error_panic_hook", "js-sys", "serde-wasm-bindgen", "wasm-bindgen", "sys_traits/wasm"] -sync = [] +path = "./lib.rs" [dependencies] -async-trait = { version = "0.1.73", optional = true } base32 = "=0.5.1" -base64 = { version = "0.21.7", optional = true } boxed_error = "0.2.3" -cache_control = { version = "0.2.0", optional = true } -# Note: Do not use the "clock" feature of chrono, as it links us to CoreFoundation on macOS. -chrono = { version = "0.4", default-features = false, features = ["std"], optional = true } -data-url = { version = "0.3.0", optional = true } +deno_cache_dir = "0.27.0" deno_error = { version = "0.7.0", features =["url"] } deno_media_type = "0.4.0" deno_path_util = "0.6.0" -sys_traits.workspace = true -http = { version = "1", optional = true } +sys_traits = { version = "0.1.24", features = ["wasm"] } indexmap = { version = "2.0.0", features = ["serde"] } log = "0.4.19" once_cell = "1.18.0" @@ -39,13 +26,7 @@ sha2 = "0.10.0" thiserror = "2" url = { version = "2.5.1", features = ["serde"] } -console_error_panic_hook = { version = "0.1.6", optional = true } -js-sys = { version = "=0.3.68", optional = true } -wasm-bindgen = { version = "=0.2.91", optional = true } -serde-wasm-bindgen = { version = "0.6.5", optional = true } - -[dev-dependencies] -pretty_assertions = "1.4.0" -sys_traits = { workspace = true, features = ["memory", "real", "getrandom", "libc", "winapi"] } -tempfile = "3.7.1" -tokio = { version = "1", features = ["rt", "macros"] } +console_error_panic_hook = "0.1.7" +js-sys = "=0.3.83" +wasm-bindgen = "=0.2.106" +serde-wasm-bindgen = "0.6.5" diff --git a/rs_lib/lib.rs b/rs_lib/lib.rs new file mode 100644 index 0000000..53d9a96 --- /dev/null +++ b/rs_lib/lib.rs @@ -0,0 +1,251 @@ +// Copyright the Deno authors. MIT license. + +use std::collections::HashMap; +use std::io::ErrorKind; +use std::path::PathBuf; +use std::rc::Rc; + +use js_sys::Object; +use js_sys::Reflect; +use js_sys::Uint8Array; +use sys_traits::EnvVar; +use sys_traits::impls::RealSys; +use sys_traits::impls::wasm_path_to_str; +use sys_traits::impls::wasm_string_to_path; +use url::Url; +use wasm_bindgen::prelude::*; + +use deno_cache_dir::CacheReadFileError; +use deno_cache_dir::Checksum; +use deno_cache_dir::HttpCache; +use deno_cache_dir::CacheEntry; +use deno_cache_dir::GlobalToLocalCopy; +use deno_cache_dir::HeadersMap; + +#[wasm_bindgen] +pub fn url_to_filename(url: &str) -> Result { + console_error_panic_hook::set_once(); + let url = parse_url(url).map_err(as_js_error)?; + deno_cache_dir::url_to_filename(&url) + .map(|s| s.to_string_lossy().to_string()) + .map_err(as_js_error) +} + +#[wasm_bindgen] +pub fn resolve_deno_dir( + maybe_custom_root: Option, +) -> Result { + console_error_panic_hook::set_once(); + deno_cache_dir::resolve_deno_dir( + &RealSys, + maybe_custom_root.map(wasm_string_to_path), + ) + .map(|path| wasm_path_to_str(&path).into_owned()) + .map_err(|e| JsValue::from(js_sys::Error::new(&e.to_string()))) +} + +#[wasm_bindgen] +pub struct GlobalHttpCache { + cache: deno_cache_dir::GlobalHttpCache, +} + +#[wasm_bindgen] +impl GlobalHttpCache { + pub fn new(path: &str) -> Self { + Self { + cache: deno_cache_dir::GlobalHttpCache::new(RealSys, PathBuf::from(path)), + } + } + + #[wasm_bindgen(js_name = getHeaders)] + pub fn get_headers(&self, url: &str) -> Result { + get_headers(&self.cache, url) + } + + pub fn get( + &self, + url: &str, + maybe_checksum: Option, + ) -> Result { + get_cache_entry(&self.cache, url, maybe_checksum.as_deref()) + } + + pub fn set( + &self, + url: &str, + headers: JsValue, + text: &[u8], + ) -> Result<(), JsValue> { + set(&self.cache, url, headers, text) + } +} + +#[wasm_bindgen] +pub struct LocalHttpCache { + cache: deno_cache_dir::LocalHttpCache, +} + +#[wasm_bindgen] +impl LocalHttpCache { + pub fn new( + local_path: String, + global_path: String, + allow_global_to_local_copy: bool, + ) -> Self { + console_error_panic_hook::set_once(); + let global = deno_cache_dir::GlobalHttpCache::new( + RealSys, + wasm_string_to_path(global_path), + ); + let jsr_url = RealSys + .env_var("JSR_URL") + .ok() + .and_then(|url| { + // ensure there is a trailing slash for the directory + let registry_url = format!("{}/", url.trim_end_matches('/')); + Url::parse(®istry_url).ok() + }) + .unwrap_or_else(|| Url::parse("https://jsr.io/").unwrap()); + let local = deno_cache_dir::LocalHttpCache::new( + wasm_string_to_path(local_path), + Rc::new(global), + if allow_global_to_local_copy { + GlobalToLocalCopy::Allow + } else { + GlobalToLocalCopy::Disallow + }, + jsr_url, + ); + Self { cache: local } + } + + #[wasm_bindgen(js_name = getHeaders)] + pub fn get_headers(&self, url: &str) -> Result { + get_headers(&self.cache, url) + } + + pub fn get( + &self, + url: &str, + maybe_checksum: Option, + ) -> Result { + get_cache_entry(&self.cache, url, maybe_checksum.as_deref()) + } + + pub fn set( + &self, + url: &str, + headers: JsValue, + text: &[u8], + ) -> Result<(), JsValue> { + set(&self.cache, url, headers, text) + } +} + +fn get_headers( + cache: &Cache, + url: &str, +) -> Result { + fn inner( + cache: &Cache, + url: &str, + ) -> std::io::Result> { + let url = parse_url(url)?; + let key = cache.cache_item_key(&url)?; + cache.read_headers(&key) + } + + inner(cache, url) + .map(|headers| match headers { + Some(headers) => serde_wasm_bindgen::to_value(&headers).unwrap(), + None => JsValue::undefined(), + }) + .map_err(as_js_error) +} + +fn get_cache_entry( + cache: &Cache, + url: &str, + maybe_checksum: Option<&str>, +) -> Result { + fn inner( + cache: &Cache, + url: &str, + maybe_checksum: Option, + ) -> std::io::Result> { + let url = parse_url(url)?; + let key = cache.cache_item_key(&url)?; + match cache.get(&key, maybe_checksum) { + Ok(Some(entry)) => Ok(Some(entry)), + Ok(None) => Ok(None), + Err(err) => match err { + CacheReadFileError::Io(err) => Err(err), + CacheReadFileError::ChecksumIntegrity(err) => { + Err(std::io::Error::new(ErrorKind::InvalidData, err.to_string())) + } + }, + } + } + + inner(cache, url, maybe_checksum.map(Checksum::new)) + .map(|text| match text { + Some(entry) => { + let content = { + let array = Uint8Array::new_with_length(entry.content.len() as u32); + array.copy_from(&entry.content); + JsValue::from(array) + }; + let headers: JsValue = { + // make it an object instead of a Map + let headers_object = Object::new(); + for (key, value) in &entry.metadata.headers { + Reflect::set( + &headers_object, + &JsValue::from_str(key), + &JsValue::from_str(value), + ) + .unwrap(); + } + JsValue::from(headers_object) + }; + let obj = Object::new(); + Reflect::set(&obj, &JsValue::from_str("content"), &content).unwrap(); + Reflect::set(&obj, &JsValue::from_str("headers"), &headers).unwrap(); + JsValue::from(obj) + } + None => JsValue::undefined(), + }) + .map_err(as_js_error) +} + +fn set( + cache: &Cache, + url: &str, + headers: JsValue, + content: &[u8], +) -> Result<(), JsValue> { + fn inner( + cache: &Cache, + url: &str, + headers: JsValue, + content: &[u8], + ) -> std::io::Result<()> { + let url = parse_url(url)?; + let headers: HashMap = + serde_wasm_bindgen::from_value(headers).map_err(|err| { + std::io::Error::new(ErrorKind::InvalidData, err.to_string()) + })?; + cache.set(&url, headers, content) + } + + inner(cache, url, headers, content).map_err(as_js_error) +} + +fn parse_url(url: &str) -> std::io::Result { + Url::parse(url) + .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e.to_string())) +} + +fn as_js_error(e: std::io::Error) -> JsValue { + JsValue::from(js_sys::Error::new(&e.to_string())) +} diff --git a/rs_lib/src/cache.rs b/rs_lib/src/cache.rs deleted file mode 100644 index 0ed6b56..0000000 --- a/rs_lib/src/cache.rs +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use deno_error::JsError; -use serde::Deserialize; -use serde::Serialize; -use std::borrow::Cow; -use std::io::ErrorKind; -use std::path::PathBuf; -use std::time::SystemTime; -use thiserror::Error; -use url::Url; - -use crate::GlobalHttpCacheRc; -use crate::LocalHttpCacheRc; -use crate::common::HeadersMap; -use crate::common::base_url_to_filename_parts; -use crate::common::checksum; -use crate::global::GlobalHttpCacheSys; -use crate::local::LocalHttpCacheSys; -use crate::sync::MaybeSend; -use crate::sync::MaybeSync; - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum GlobalToLocalCopy { - /// When using a local cache (vendor folder), allow the cache to - /// copy from the global cache into the local one. - Allow, - /// Disallow copying from the global to the local cache. This is - /// useful for the LSP because we want to ensure that checksums - /// are evaluated for JSR dependencies, which is difficult to do - /// in the LSP. This could be improved in the future to not require - /// this - Disallow, -} - -impl GlobalToLocalCopy { - pub fn is_true(&self) -> bool { - matches!(self, GlobalToLocalCopy::Allow) - } -} - -#[derive(Debug, Error, JsError)] -#[class(type)] -#[error("Integrity check failed for {}\n\nActual: {}\nExpected: {}", .url, .actual, .expected)] -pub struct ChecksumIntegrityError { - pub url: Url, - pub actual: String, - pub expected: String, -} - -#[derive(Debug, Clone, Copy)] -pub struct Checksum<'a>(&'a str); - -impl<'a> Checksum<'a> { - pub fn new(checksum: &'a str) -> Self { - Self(checksum) - } - - pub fn as_str(&self) -> &str { - self.0 - } - - pub fn check( - &self, - url: &Url, - content: &[u8], - ) -> Result<(), Box> { - let actual = checksum(content); - if self.as_str() != actual { - Err(Box::new(ChecksumIntegrityError { - url: url.clone(), - expected: self.as_str().to_string(), - actual, - })) - } else { - Ok(()) - } - } -} - -/// Turn provided `url` into a hashed filename. -/// URLs can contain a lot of characters that cannot be used -/// in filenames (like "?", "#", ":"), so in order to cache -/// them properly they are deterministically hashed into ASCII -/// strings. -pub fn url_to_filename(url: &Url) -> std::io::Result { - // Replaces port part with a special string token (because - // ":" cannot be used in filename on some platforms). - // Ex: $DENO_DIR/remote/https/deno.land/ - let Some(cache_parts) = base_url_to_filename_parts(url, "_PORT") else { - return Err(std::io::Error::new( - ErrorKind::InvalidInput, - format!("Can't convert url (\"{}\") to filename.", url), - )); - }; - - let rest_str = if let Some(query) = url.query() { - let mut rest_str = - String::with_capacity(url.path().len() + 1 + query.len()); - rest_str.push_str(url.path()); - rest_str.push('?'); - rest_str.push_str(query); - Cow::Owned(rest_str) - } else { - Cow::Borrowed(url.path()) - }; - - // NOTE: fragment is omitted on purpose - it's not taken into - // account when caching - it denotes parts of webpage, which - // in case of static resources doesn't make much sense - let hashed_filename = checksum(rest_str.as_bytes()); - let capacity = cache_parts.iter().map(|s| s.len() + 1).sum::() - + 1 - + hashed_filename.len(); - let mut cache_filename = PathBuf::with_capacity(capacity); - cache_filename.extend(cache_parts.iter().map(|s| s.as_ref())); - cache_filename.push(hashed_filename); - debug_assert_eq!(cache_filename.capacity(), capacity); - Ok(cache_filename) -} - -#[derive(Debug, Error, JsError)] -pub enum CacheReadFileError { - #[class(inherit)] - #[error(transparent)] - Io(#[from] std::io::Error), - #[class(inherit)] - #[error(transparent)] - ChecksumIntegrity(Box), -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct SerializedCachedUrlMetadata { - pub headers: HeadersMap, - pub url: String, - /// Number of seconds since the UNIX epoch. - #[serde(default)] - pub time: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CacheEntry { - pub metadata: SerializedCachedUrlMetadata, - pub content: Cow<'static, [u8]>, -} - -/// Computed cache key, which can help reduce the work of computing the cache key multiple times. -pub struct HttpCacheItemKey<'a> { - // The key is specific to the implementation of HttpCache, - // so keep these private to the module. For example, the - // fact that these may be stored in a file is an implementation - // detail. - #[cfg(debug_assertions)] - pub(super) is_local_key: bool, - pub(super) url: &'a Url, - /// This will be set all the time for the global cache, but it - /// won't ever be set for the local cache because that also needs - /// header information to determine the final path. - pub(super) file_path: Option, -} - -#[allow(clippy::disallowed_types)] -pub type HttpCacheRc = crate::sync::MaybeArc; - -pub trait HttpCache: MaybeSend + MaybeSync + std::fmt::Debug { - /// A pre-computed key for looking up items in the cache. - fn cache_item_key<'a>( - &self, - url: &'a Url, - ) -> std::io::Result>; - - fn contains(&self, url: &Url) -> bool; - fn set( - &self, - url: &Url, - headers: HeadersMap, - content: &[u8], - ) -> std::io::Result<()>; - fn get( - &self, - key: &HttpCacheItemKey, - maybe_checksum: Option, - ) -> Result, CacheReadFileError>; - fn read_modified_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result>; - /// Reads the headers for the cache item. - fn read_headers( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result>; - /// Reads the time the item was downloaded to the cache. - fn read_download_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result>; -} - -#[derive(Debug, Clone)] -pub enum GlobalOrLocalHttpCache { - Global(GlobalHttpCacheRc), - Local(LocalHttpCacheRc), -} - -impl From> - for GlobalOrLocalHttpCache -{ - fn from(global: GlobalHttpCacheRc) -> Self { - Self::Global(global) - } -} - -impl From> - for GlobalOrLocalHttpCache -{ - fn from(local: LocalHttpCacheRc) -> Self { - Self::Local(local) - } -} - -impl HttpCache - for GlobalOrLocalHttpCache -{ - fn read_headers( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - match self { - GlobalOrLocalHttpCache::Global(global) => global.read_headers(key), - GlobalOrLocalHttpCache::Local(local) => local.read_headers(key), - } - } - - fn cache_item_key<'a>( - &self, - url: &'a Url, - ) -> std::io::Result> { - match self { - GlobalOrLocalHttpCache::Global(global) => global.cache_item_key(url), - GlobalOrLocalHttpCache::Local(local) => local.cache_item_key(url), - } - } - - fn contains(&self, url: &Url) -> bool { - match self { - GlobalOrLocalHttpCache::Global(global) => global.contains(url), - GlobalOrLocalHttpCache::Local(local) => local.contains(url), - } - } - - fn set( - &self, - url: &Url, - headers: HeadersMap, - content: &[u8], - ) -> std::io::Result<()> { - match self { - GlobalOrLocalHttpCache::Global(global) => { - global.set(url, headers, content) - } - GlobalOrLocalHttpCache::Local(local) => local.set(url, headers, content), - } - } - - fn get( - &self, - key: &HttpCacheItemKey, - maybe_checksum: Option, - ) -> Result, CacheReadFileError> { - match self { - GlobalOrLocalHttpCache::Global(global) => global.get(key, maybe_checksum), - GlobalOrLocalHttpCache::Local(local) => local.get(key, maybe_checksum), - } - } - - fn read_modified_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - match self { - GlobalOrLocalHttpCache::Global(global) => global.read_modified_time(key), - GlobalOrLocalHttpCache::Local(local) => local.read_modified_time(key), - } - } - - fn read_download_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - match self { - GlobalOrLocalHttpCache::Global(global) => global.read_download_time(key), - GlobalOrLocalHttpCache::Local(local) => local.read_download_time(key), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn deserialized_no_time() { - let json = r#"{ - "headers": { - "content-type": "application/javascript" - }, - "url": "https://deno.land/std/http/file_server.ts" - }"#; - let data: SerializedCachedUrlMetadata = serde_json::from_str(json).unwrap(); - assert_eq!( - data, - SerializedCachedUrlMetadata { - headers: HeadersMap::from([( - "content-type".to_string(), - "application/javascript".to_string() - )]), - time: None, - url: "https://deno.land/std/http/file_server.ts".to_string(), - } - ); - } - - #[test] - fn serialize_deserialize_time() { - let json = r#"{ - "headers": { - "content-type": "application/javascript" - }, - "url": "https://deno.land/std/http/file_server.ts", - "time": 123456789 - }"#; - let data: SerializedCachedUrlMetadata = serde_json::from_str(json).unwrap(); - let expected = SerializedCachedUrlMetadata { - headers: HeadersMap::from([( - "content-type".to_string(), - "application/javascript".to_string(), - )]), - time: Some(123456789), - url: "https://deno.land/std/http/file_server.ts".to_string(), - }; - assert_eq!(data, expected); - } -} diff --git a/rs_lib/src/common.rs b/rs_lib/src/common.rs deleted file mode 100644 index bcffd16..0000000 --- a/rs_lib/src/common.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::borrow::Cow; -use std::collections::HashMap; - -use url::Url; - -// TODO(ry) HTTP headers are not unique key, value pairs. There may be more than -// one header line with the same key. This should be changed to something like -// Vec<(String, String)> -pub type HeadersMap = HashMap; - -pub fn base_url_to_filename_parts<'a>( - url: &'a Url, - port_separator: &str, -) -> Option>> { - let mut out = Vec::with_capacity(2); - - let scheme = url.scheme(); - - match scheme { - "http" | "https" => { - out.push(Cow::Borrowed(scheme)); - - let host = url.host_str().unwrap(); - let host_port = match url.port() { - // underscores are not allowed in domains, so adding one here is fine - Some(port) => Cow::Owned(format!("{host}{port_separator}{port}")), - None => Cow::Borrowed(host), - }; - out.push(host_port); - } - "data" | "blob" => { - out.push(Cow::Borrowed(scheme)); - } - scheme => { - log::debug!("Don't know how to create cache name for scheme: {}", scheme); - return None; - } - }; - - Some(out) -} - -pub fn checksum(v: &[u8]) -> String { - use sha2::Digest; - use sha2::Sha256; - - let mut hasher = Sha256::new(); - hasher.update(v); - format!("{:x}", hasher.finalize()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_gen() { - let actual = checksum(b"hello world"); - assert_eq!( - actual, - "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" - ); - } -} diff --git a/rs_lib/src/deno_dir.rs b/rs_lib/src/deno_dir.rs deleted file mode 100644 index 5c671b9..0000000 --- a/rs_lib/src/deno_dir.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::path::PathBuf; - -use sys_traits::EnvCacheDir; -use sys_traits::EnvCurrentDir; -use sys_traits::EnvHomeDir; -use sys_traits::EnvVar; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum DenoDirResolutionError { - #[error( - "Could not resolve global Deno cache directory. Please make sure that either the DENO_DIR environment variable is set or the cache directory is available." - )] - NoCacheOrHomeDir, - #[error( - "Could not resolve global Deno cache directory because the current working directory could not be resolved. Please set the DENO_DIR environment variable and ensure it is pointing at an absolute path." - )] - FailedCwd { - #[source] - source: std::io::Error, - }, -} - -#[sys_traits::auto_impl] -pub trait ResolveDenoDirSys: - EnvCacheDir + EnvHomeDir + EnvVar + EnvCurrentDir -{ -} - -pub fn resolve_deno_dir( - sys: &Sys, - maybe_custom_root: Option, -) -> Result { - let maybe_custom_root = - maybe_custom_root.or_else(|| sys.env_var_path("DENO_DIR")); - let root: PathBuf = if let Some(root) = maybe_custom_root { - root - } else if let Some(xdg_cache_dir) = sys.env_var_path("XDG_CACHE_HOME") { - xdg_cache_dir.join("deno") - } else if let Some(cache_dir) = sys.env_cache_dir() { - // We use the OS cache dir because all files deno writes are cache files - // Once that changes we need to start using different roots if DENO_DIR - // is not set, and keep a single one if it is. - cache_dir.join("deno") - } else if let Some(home_dir) = sys.env_home_dir() { - // fallback path - home_dir.join(".deno") - } else { - return Err(DenoDirResolutionError::NoCacheOrHomeDir); - }; - let root = if root.is_absolute() { - root - } else { - sys - .env_current_dir() - .map_err(|source| DenoDirResolutionError::FailedCwd { source })? - .join(root) - }; - Ok(root) -} diff --git a/rs_lib/src/file_fetcher/auth_tokens.rs b/rs_lib/src/file_fetcher/auth_tokens.rs deleted file mode 100644 index 733e479..0000000 --- a/rs_lib/src/file_fetcher/auth_tokens.rs +++ /dev/null @@ -1,361 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::borrow::Cow; -use std::fmt; -use std::net::IpAddr; -use std::net::Ipv4Addr; -use std::net::Ipv6Addr; -use std::net::SocketAddr; -use std::str::FromStr; - -use base64::Engine; -use base64::prelude::BASE64_STANDARD; -use log::debug; -use log::error; -use sys_traits::EnvVar; -use url::Url; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum AuthTokenData { - Bearer(String), - Basic { username: String, password: String }, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AuthToken { - host: AuthDomain, - token: AuthTokenData, -} - -impl fmt::Display for AuthToken { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match &self.token { - AuthTokenData::Bearer(token) => write!(f, "Bearer {token}"), - AuthTokenData::Basic { username, password } => { - let credentials = format!("{username}:{password}"); - write!(f, "Basic {}", BASE64_STANDARD.encode(credentials)) - } - } - } -} - -/// A structure which contains bearer tokens that can be used when sending -/// requests to websites, intended to authorize access to private resources -/// such as remote modules. -#[derive(Debug, Clone)] -pub struct AuthTokens(Vec); - -/// An authorization domain, either an exact or suffix match. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum AuthDomain { - Ip(IpAddr), - IpPort(SocketAddr), - /// Suffix match, no dot. May include a port. - Suffix(Cow<'static, str>), -} - -impl From for AuthDomain { - fn from(value: T) -> Self { - let s = value.to_string().to_lowercase(); - if let Ok(ip) = SocketAddr::from_str(&s) { - return AuthDomain::IpPort(ip); - }; - if s.starts_with('[') && s.ends_with(']') { - if let Ok(ip) = Ipv6Addr::from_str(&s[1..s.len() - 1]) { - return AuthDomain::Ip(ip.into()); - } - } else if let Ok(ip) = Ipv4Addr::from_str(&s) { - return AuthDomain::Ip(ip.into()); - } - if let Some(s) = s.strip_prefix('.') { - AuthDomain::Suffix(Cow::Owned(s.to_owned())) - } else { - AuthDomain::Suffix(Cow::Owned(s)) - } - } -} - -impl AuthDomain { - pub fn matches(&self, specifier: &Url) -> bool { - let Some(host) = specifier.host_str() else { - return false; - }; - match *self { - Self::Ip(ip) => { - let AuthDomain::Ip(parsed) = AuthDomain::from(host) else { - return false; - }; - ip == parsed && specifier.port().is_none() - } - Self::IpPort(ip) => { - let AuthDomain::Ip(parsed) = AuthDomain::from(host) else { - return false; - }; - ip.ip() == parsed && specifier.port() == Some(ip.port()) - } - Self::Suffix(ref suffix) => { - let hostname = if let Some(port) = specifier.port() { - Cow::Owned(format!("{}:{}", host, port)) - } else { - Cow::Borrowed(host) - }; - - if suffix.len() == hostname.len() { - return suffix == &hostname; - } - - // If it's a suffix match, ensure a dot - if hostname.ends_with(suffix.as_ref()) - && hostname.ends_with(&format!(".{suffix}")) - { - return true; - } - - false - } - } - } -} - -impl AuthTokens { - pub fn new_from_sys(sys: &TSys) -> Self { - Self::new(sys.env_var("DENO_AUTH_TOKENS").ok()) - } - - /// Create a new set of tokens based on the provided string. It is intended - /// that the string be the value of an environment variable and the string is - /// parsed for token values. The string is expected to be a semi-colon - /// separated string, where each value is `{token}@{hostname}`. - pub fn new(maybe_tokens_str: Option) -> Self { - let mut tokens = Vec::new(); - if let Some(tokens_str) = maybe_tokens_str { - for token_str in tokens_str.trim().split(';') { - if token_str.contains('@') { - let mut iter = token_str.rsplitn(2, '@'); - let host = AuthDomain::from(iter.next().unwrap()); - let token = iter.next().unwrap(); - if token.contains(':') { - let mut iter = token.rsplitn(2, ':'); - let password = iter.next().unwrap().to_owned(); - let username = iter.next().unwrap().to_owned(); - tokens.push(AuthToken { - host, - token: AuthTokenData::Basic { username, password }, - }); - } else { - tokens.push(AuthToken { - host, - token: AuthTokenData::Bearer(token.to_string()), - }); - } - } else { - error!("Badly formed auth token discarded."); - } - } - debug!("Parsed {} auth token(s).", tokens.len()); - } - - Self(tokens) - } - - /// Attempt to match the provided specifier to the tokens in the set. The - /// matching occurs from the right of the hostname plus port, irrespective of - /// scheme. For example `https://www.deno.land:8080/` would match a token - /// with a host value of `deno.land:8080` but not match `www.deno.land`. The - /// matching is case insensitive. - pub fn get(&self, specifier: &Url) -> Option<&AuthToken> { - self.0.iter().find(|t| t.host.matches(specifier)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_auth_token() { - let auth_tokens = AuthTokens::new(Some("abc123@deno.land".to_string())); - let fixture = Url::parse("https://deno.land/x/mod.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer abc123" - ); - let fixture = Url::parse("https://www.deno.land/x/mod.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer abc123".to_string() - ); - let fixture = Url::parse("http://127.0.0.1:8080/x/mod.ts").unwrap(); - assert_eq!(auth_tokens.get(&fixture), None); - let fixture = Url::parse("https://deno.land.example.com/x/mod.ts").unwrap(); - assert_eq!(auth_tokens.get(&fixture), None); - let fixture = Url::parse("https://deno.land:8080/x/mod.ts").unwrap(); - assert_eq!(auth_tokens.get(&fixture), None); - } - - #[test] - fn test_auth_tokens_multiple() { - let auth_tokens = - AuthTokens::new(Some("abc123@deno.land;def456@example.com".to_string())); - let fixture = Url::parse("https://deno.land/x/mod.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer abc123".to_string() - ); - let fixture = Url::parse("http://example.com/a/file.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer def456".to_string() - ); - } - - #[test] - fn test_auth_tokens_space() { - let auth_tokens = AuthTokens::new(Some( - " abc123@deno.land;def456@example.com\t".to_string(), - )); - let fixture = Url::parse("https://deno.land/x/mod.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer abc123".to_string() - ); - let fixture = Url::parse("http://example.com/a/file.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer def456".to_string() - ); - } - - #[test] - fn test_auth_tokens_newline() { - let auth_tokens = AuthTokens::new(Some( - "\nabc123@deno.land;def456@example.com\n".to_string(), - )); - let fixture = Url::parse("https://deno.land/x/mod.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer abc123".to_string() - ); - let fixture = Url::parse("http://example.com/a/file.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer def456".to_string() - ); - } - - #[test] - fn test_auth_tokens_port() { - let auth_tokens = - AuthTokens::new(Some("abc123@deno.land:8080".to_string())); - let fixture = Url::parse("https://deno.land/x/mod.ts").unwrap(); - assert_eq!(auth_tokens.get(&fixture), None); - let fixture = Url::parse("http://deno.land:8080/x/mod.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer abc123".to_string() - ); - } - - #[test] - fn test_auth_tokens_contain_at() { - let auth_tokens = AuthTokens::new(Some("abc@123@deno.land".to_string())); - let fixture = Url::parse("https://deno.land/x/mod.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Bearer abc@123".to_string() - ); - } - - #[test] - fn test_auth_token_basic() { - let auth_tokens = AuthTokens::new(Some("abc:123@deno.land".to_string())); - let fixture = Url::parse("https://deno.land/x/mod.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Basic YWJjOjEyMw==" - ); - let fixture = Url::parse("https://www.deno.land/x/mod.ts").unwrap(); - assert_eq!( - auth_tokens.get(&fixture).unwrap().to_string(), - "Basic YWJjOjEyMw==".to_string() - ); - let fixture = Url::parse("http://127.0.0.1:8080/x/mod.ts").unwrap(); - assert_eq!(auth_tokens.get(&fixture), None); - let fixture = Url::parse("https://deno.land.example.com/x/mod.ts").unwrap(); - assert_eq!(auth_tokens.get(&fixture), None); - let fixture = Url::parse("https://deno.land:8080/x/mod.ts").unwrap(); - assert_eq!(auth_tokens.get(&fixture), None); - } - - #[test] - fn test_parse_ip() { - let ip = AuthDomain::from("[2001:db8:a::123]"); - assert_eq!("Ip(2001:db8:a::123)", format!("{ip:?}")); - let ip = AuthDomain::from("[2001:db8:a::123]:8080"); - assert_eq!("IpPort([2001:db8:a::123]:8080)", format!("{ip:?}")); - let ip = AuthDomain::from("1.1.1.1"); - assert_eq!("Ip(1.1.1.1)", format!("{ip:?}")); - } - - #[test] - fn test_case_insensitive() { - let domain = AuthDomain::from("EXAMPLE.com"); - assert!(domain.matches(&Url::parse("http://example.com").unwrap())); - assert!(domain.matches(&Url::parse("http://example.COM").unwrap())); - } - - #[test] - fn test_matches() { - let candidates = [ - "example.com", - "www.example.com", - "1.1.1.1", - "[2001:db8:a::123]", - // These will never match - "example.com.evil.com", - "1.1.1.1.evil.com", - "notexample.com", - "www.notexample.com", - ]; - let domains = [ - ("example.com", vec!["example.com", "www.example.com"]), - (".example.com", vec!["example.com", "www.example.com"]), - ("www.example.com", vec!["www.example.com"]), - ("1.1.1.1", vec!["1.1.1.1"]), - ("[2001:db8:a::123]", vec!["[2001:db8:a::123]"]), - ]; - let url = |c: &str| Url::parse(&format!("http://{c}")).unwrap(); - let url_port = |c: &str| Url::parse(&format!("http://{c}:8080")).unwrap(); - - // Generate each candidate with and without a port - let candidates = candidates - .into_iter() - .flat_map(|c| [url(c), url_port(c)]) - .collect::>(); - - for (domain, expected_domain) in domains { - // Test without a port -- all candidates return without a port - let auth_domain = AuthDomain::from(domain); - let actual = candidates - .iter() - .filter(|c| auth_domain.matches(c)) - .cloned() - .collect::>(); - let expected = expected_domain.iter().map(|u| url(u)).collect::>(); - assert_eq!(actual, expected); - - // Test with a port, all candidates return with a port - let auth_domain = AuthDomain::from(&format!("{domain}:8080")); - let actual = candidates - .iter() - .filter(|c| auth_domain.matches(c)) - .cloned() - .collect::>(); - let expected = expected_domain - .iter() - .map(|u| url_port(u)) - .collect::>(); - assert_eq!(actual, expected); - } - } -} diff --git a/rs_lib/src/file_fetcher/http_util.rs b/rs_lib/src/file_fetcher/http_util.rs deleted file mode 100644 index 539f985..0000000 --- a/rs_lib/src/file_fetcher/http_util.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::time::Duration; -use std::time::SystemTime; - -use cache_control::Cachability; -use cache_control::CacheControl; -use chrono::DateTime; - -use crate::common::HeadersMap; - -/// A structure used to determine if a entity in the http cache can be used. -/// -/// This is heavily influenced by -/// which is BSD -/// 2-Clause Licensed and copyright Kornel LesiÅ„ski -pub struct CacheSemantics { - cache_control: CacheControl, - cached: SystemTime, - headers: HeadersMap, - now: SystemTime, -} - -impl CacheSemantics { - pub fn new(headers: HeadersMap, cached: SystemTime, now: SystemTime) -> Self { - let cache_control = headers - .get("cache-control") - .map(|v| CacheControl::from_value(v).unwrap_or_default()) - .unwrap_or_default(); - Self { - cache_control, - cached, - headers, - now, - } - } - - fn age(&self) -> Duration { - let mut age = self.age_header_value(); - - if let Ok(resident_time) = self.now.duration_since(self.cached) { - age += resident_time; - } - - age - } - - fn age_header_value(&self) -> Duration { - Duration::from_secs( - self - .headers - .get("age") - .and_then(|v| v.parse().ok()) - .unwrap_or(0), - ) - } - - fn is_stale(&self) -> bool { - self.max_age() <= self.age() - } - - fn max_age(&self) -> Duration { - if self.cache_control.cachability == Some(Cachability::NoCache) { - return Duration::from_secs(0); - } - - if self.headers.get("vary").map(|s| s.trim()) == Some("*") { - return Duration::from_secs(0); - } - - if let Some(max_age) = self.cache_control.max_age { - return max_age; - } - - let default_min_ttl = Duration::from_secs(0); - - let server_date = self.raw_server_date(); - if let Some(expires) = self.headers.get("expires") { - return match DateTime::parse_from_rfc2822(expires) { - Err(_) => Duration::from_secs(0), - Ok(expires) => { - let expires = SystemTime::UNIX_EPOCH - + Duration::from_secs(expires.timestamp().max(0) as _); - return default_min_ttl - .max(expires.duration_since(server_date).unwrap_or_default()); - } - }; - } - - if let Some(last_modified) = self.headers.get("last-modified") - && let Ok(last_modified) = DateTime::parse_from_rfc2822(last_modified) - { - let last_modified = SystemTime::UNIX_EPOCH - + Duration::from_secs(last_modified.timestamp().max(0) as _); - if let Ok(diff) = server_date.duration_since(last_modified) { - let secs_left = diff.as_secs() as f64 * 0.1; - return default_min_ttl.max(Duration::from_secs(secs_left as _)); - } - } - - default_min_ttl - } - - fn raw_server_date(&self) -> SystemTime { - self - .headers - .get("date") - .and_then(|d| DateTime::parse_from_rfc2822(d).ok()) - .and_then(|d| { - SystemTime::UNIX_EPOCH - .checked_add(Duration::from_secs(d.timestamp() as _)) - }) - .unwrap_or(self.cached) - } - - /// Returns true if the cached value is "fresh" respecting cached headers, - /// otherwise returns false. - pub fn should_use(&self) -> bool { - if self.cache_control.cachability == Some(Cachability::NoCache) { - return false; - } - - if let Some(max_age) = self.cache_control.max_age - && self.age() > max_age - { - return false; - } - - if let Some(min_fresh) = self.cache_control.min_fresh - && self.time_to_live() < min_fresh - { - return false; - } - - if self.is_stale() { - let has_max_stale = self.cache_control.max_stale.is_some(); - let allows_stale = has_max_stale - && self - .cache_control - .max_stale - .map(|val| val > self.age() - self.max_age()) - .unwrap_or(true); - if !allows_stale { - return false; - } - } - - true - } - - fn time_to_live(&self) -> Duration { - self.max_age().checked_sub(self.age()).unwrap_or_default() - } -} diff --git a/rs_lib/src/file_fetcher/mod.rs b/rs_lib/src/file_fetcher/mod.rs deleted file mode 100644 index 5e560af..0000000 --- a/rs_lib/src/file_fetcher/mod.rs +++ /dev/null @@ -1,1366 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::borrow::Cow; -use std::collections::HashMap; -use std::io::Read; -use std::path::Path; -use std::path::PathBuf; -use std::time::SystemTime; - -use boxed_error::Boxed; -use data_url::DataUrl; -use deno_error::JsError; -use deno_media_type::MediaType; -use deno_path_util::url_to_file_path; -use http::header; -use http::header::ACCEPT; -use http::header::AUTHORIZATION; -use http::header::IF_NONE_MATCH; -use http::header::LOCATION; -use log::debug; -use sys_traits::FsFileMetadata; -use sys_traits::FsMetadataValue; -use sys_traits::FsOpen; -use sys_traits::OpenOptions; -use sys_traits::SystemTimeNow; -use thiserror::Error; -use url::Url; - -use self::http_util::CacheSemantics; -use crate::CacheEntry; -use crate::CacheReadFileError; -use crate::Checksum; -use crate::ChecksumIntegrityError; -use crate::cache::HttpCacheRc; -use crate::common::HeadersMap; -use crate::sync::MaybeSend; -use crate::sync::MaybeSync; - -mod auth_tokens; -mod http_util; - -pub use auth_tokens::AuthDomain; -pub use auth_tokens::AuthToken; -pub use auth_tokens::AuthTokenData; -pub use auth_tokens::AuthTokens; -pub use http::HeaderMap; -pub use http::HeaderName; -pub use http::HeaderValue; -pub use http::StatusCode; - -/// Indicates how cached source files should be handled. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum CacheSetting { - /// Only the cached files should be used. Any files not in the cache will - /// error. This is the equivalent of `--cached-only` in the CLI. - Only, - /// No cached source files should be used, and all files should be reloaded. - /// This is the equivalent of `--reload` in the CLI. - ReloadAll, - /// Only some cached resources should be used. This is the equivalent of - /// `--reload=jsr:@std/http/file-server` or - /// `--reload=jsr:@std/http/file-server,jsr:@std/assert/assert-equals`. - ReloadSome(Vec), - /// The usability of a cached value is determined by analyzing the cached - /// headers and other metadata associated with a cached response, reloading - /// any cached "non-fresh" cached responses. - RespectHeaders, - /// The cached source files should be used for local modules. This is the - /// default behavior of the CLI. - Use, -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum FileOrRedirect { - File(File), - Redirect(Url), -} - -impl FileOrRedirect { - fn from_deno_cache_entry( - url: &Url, - cache_entry: CacheEntry, - ) -> Result { - if let Some(redirect_to) = cache_entry.metadata.headers.get("location") { - let redirect = - url - .join(redirect_to) - .map_err(|source| RedirectResolutionError { - url: url.clone(), - location: redirect_to.clone(), - source, - })?; - Ok(FileOrRedirect::Redirect(redirect)) - } else { - Ok(FileOrRedirect::File(File { - url: url.clone(), - mtime: None, - maybe_headers: Some(cache_entry.metadata.headers), - #[allow(clippy::disallowed_types)] // ok for source - source: std::sync::Arc::from(cache_entry.content), - loaded_from: LoadedFrom::Cache, - })) - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum CachedOrRedirect { - Cached, - Redirect(Url), -} - -impl From for CachedOrRedirect { - fn from(value: FileOrRedirect) -> Self { - match value { - FileOrRedirect::File(_) => CachedOrRedirect::Cached, - FileOrRedirect::Redirect(url) => CachedOrRedirect::Redirect(url), - } - } -} - -#[allow(clippy::disallowed_types)] // ok for source -type FileSource = std::sync::Arc<[u8]>; - -/// A structure representing a source file. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct File { - /// The _final_ specifier for the file. The requested specifier and the final - /// specifier maybe different for remote files that have been redirected. - pub url: Url, - pub mtime: Option, - pub maybe_headers: Option>, - /// The source of the file. - pub source: FileSource, - - /// Where the file was loaded from. - pub loaded_from: LoadedFrom, -} - -#[derive(Debug, Clone, PartialEq, Eq, Copy)] -pub enum LoadedFrom { - /// The module was loaded from a remote source. - Remote, - /// The module was loaded from a local source. - Local, - /// The module was loaded from a cache for remote sources. - Cache, - /// The source of the module is unknown. - Unknown, -} - -impl File { - pub fn resolve_media_type_and_charset(&self) -> (MediaType, Option<&str>) { - deno_media_type::resolve_media_type_and_charset_from_content_type( - &self.url, - self - .maybe_headers - .as_ref() - .and_then(|h| h.get("content-type")) - .map(|v| v.as_str()), - ) - } -} - -#[allow(clippy::disallowed_types)] -pub type MemoryFilesRc = crate::sync::MaybeArc; - -pub trait MemoryFiles: std::fmt::Debug + MaybeSend + MaybeSync { - fn get(&self, url: &Url) -> Option; -} - -/// Implementation of `MemoryFiles` that always returns `None`. -#[derive(Debug, Clone, Default)] -pub struct NullMemoryFiles; - -impl MemoryFiles for NullMemoryFiles { - fn get(&self, _url: &Url) -> Option { - None - } -} - -#[derive(Debug, PartialEq, Eq)] -pub enum SendResponse { - NotModified, - Redirect(HeaderMap), - Success(HeaderMap, Vec), -} - -#[derive(Debug)] -pub enum SendError { - Failed(Box), - NotFound, - StatusCode(http::StatusCode), -} - -#[derive(Debug, Error, JsError)] -#[class(inherit)] -#[error("Failed resolving redirect from '{url}' to '{location}'.")] -pub struct RedirectResolutionError { - pub url: Url, - pub location: String, - #[source] - #[inherit] - pub source: url::ParseError, -} - -#[derive(Debug, Error, JsError)] -#[class(inherit)] -#[error("Unable to decode data url.")] -pub struct DataUrlDecodeError { - #[source] - source: DataUrlDecodeSourceError, -} - -#[derive(Debug, Error, JsError)] -#[class(uri)] -pub enum DataUrlDecodeSourceError { - #[error(transparent)] - DataUrl(data_url::DataUrlError), - #[error(transparent)] - InvalidBase64(data_url::forgiving_base64::InvalidBase64), -} - -#[derive(Debug, Error, JsError)] -#[class(inherit)] -#[error("Failed reading cache entry for '{url}'.")] -pub struct CacheReadError { - pub url: Url, - #[source] - #[inherit] - pub source: std::io::Error, -} - -#[derive(Debug, Error, JsError)] -#[class(generic)] -#[error("Failed reading location header for '{}'{}", .request_url, .maybe_location.as_ref().map(|location| format!(" to '{}'", location)).unwrap_or_default())] -pub struct RedirectHeaderParseError { - pub request_url: Url, - pub maybe_location: Option, - #[source] - pub maybe_source: Option>, -} - -#[derive(Debug, Error, JsError)] -#[class(inherit)] -#[error("Import '{url}' failed.")] -pub struct FailedReadingLocalFileError { - pub url: Url, - #[source] - #[inherit] - pub source: std::io::Error, -} - -#[derive(Debug, Error, JsError)] -#[class("Http")] -#[error("Fetch '{0}' failed, too many redirects.")] -pub struct TooManyRedirectsError(pub Url); - -// this message list additional `npm` and `jsr` schemes, but they should actually be handled -// before `file_fetcher.rs` APIs are even hit. -#[derive(Debug, Error, JsError)] -#[class(type)] -#[error( - "Unsupported scheme \"{scheme}\" for module \"{url}\". Supported schemes:\n - \"blob\"\n - \"data\"\n - \"file\"\n - \"http\"\n - \"https\"\n - \"jsr\"\n - \"npm\"" -)] -pub struct UnsupportedSchemeError { - pub scheme: String, - pub url: Url, -} - -/// Gets if the provided scheme was valid. -pub fn is_valid_scheme(scheme: &str) -> bool { - matches!( - scheme, - "blob" | "data" | "file" | "http" | "https" | "jsr" | "npm" - ) -} - -#[derive(Debug, Boxed, JsError)] -pub struct FetchNoFollowError(pub Box); - -#[derive(Debug, Error, JsError)] -pub enum FetchNoFollowErrorKind { - #[class(inherit)] - #[error(transparent)] - UrlToFilePath(#[from] deno_path_util::UrlToFilePathError), - #[class("NotFound")] - #[error("Import '{0}' failed, not found.")] - NotFound(Url), - #[class(generic)] - #[error("Import '{url}' failed.")] - ReadingBlobUrl { - url: Url, - #[source] - source: std::io::Error, - }, - #[class(inherit)] - #[error(transparent)] - ReadingFile(#[from] FailedReadingLocalFileError), - #[class(generic)] - #[error("Import '{url}' failed.")] - FetchingRemote { - url: Url, - #[source] - source: Box, - }, - #[class(generic)] - #[error("Import '{url}' failed: {status_code}")] - ClientError { - url: Url, - status_code: http::StatusCode, - }, - #[class("NoRemote")] - #[error( - "A remote specifier was requested: \"{0}\", but --no-remote is specified." - )] - NoRemote(Url), - #[class(inherit)] - #[error(transparent)] - DataUrlDecode(DataUrlDecodeError), - #[class(inherit)] - #[error(transparent)] - RedirectResolution(#[from] RedirectResolutionError), - #[class(inherit)] - #[error(transparent)] - ChecksumIntegrity(#[from] ChecksumIntegrityError), - #[class(inherit)] - #[error(transparent)] - CacheRead(#[from] CacheReadError), - #[class(generic)] - #[error("Failed caching '{url}'.")] - CacheSave { - url: Url, - #[source] - source: std::io::Error, - }, - // this message list additional `npm` and `jsr` schemes, but they should actually be handled - // before `file_fetcher.rs` APIs are even hit. - #[class(inherit)] - #[error(transparent)] - UnsupportedScheme(#[from] UnsupportedSchemeError), - #[class(type)] - #[error(transparent)] - RedirectHeaderParse(#[from] RedirectHeaderParseError), - #[class("NotCached")] - #[error( - "Specifier not found in cache: \"{url}\", --cached-only is specified." - )] - NotCached { url: Url }, - #[class(type)] - #[error("Failed setting header '{name}'.")] - InvalidHeader { - name: &'static str, - #[source] - source: header::InvalidHeaderValue, - }, -} - -#[derive(Debug, Boxed, JsError)] -pub struct FetchCachedError(pub Box); - -#[derive(Debug, Error, JsError)] -pub enum FetchCachedErrorKind { - #[class(inherit)] - #[error(transparent)] - TooManyRedirects(TooManyRedirectsError), - #[class(inherit)] - #[error(transparent)] - ChecksumIntegrity(#[from] ChecksumIntegrityError), - #[class(inherit)] - #[error(transparent)] - CacheRead(#[from] CacheReadError), - #[class(inherit)] - #[error(transparent)] - RedirectResolution(#[from] RedirectResolutionError), -} - -#[derive(Debug, Boxed, JsError)] -pub struct FetchLocalError(pub Box); - -#[derive(Debug, Error, JsError)] -pub enum FetchLocalErrorKind { - #[class(inherit)] - #[error(transparent)] - UrlToFilePath(#[from] deno_path_util::UrlToFilePathError), - #[class(inherit)] - #[error(transparent)] - ReadingFile(#[from] FailedReadingLocalFileError), -} - -impl From for FetchNoFollowError { - fn from(err: FetchLocalError) -> Self { - match err.into_kind() { - FetchLocalErrorKind::UrlToFilePath(err) => err.into(), - FetchLocalErrorKind::ReadingFile(err) => err.into(), - } - } -} - -#[derive(Debug, Boxed, JsError)] -struct FetchCachedNoFollowError(pub Box); - -#[derive(Debug, Error, JsError)] -enum FetchCachedNoFollowErrorKind { - #[class(inherit)] - #[error(transparent)] - ChecksumIntegrity(ChecksumIntegrityError), - #[class(inherit)] - #[error(transparent)] - CacheRead(#[from] CacheReadError), - #[class(inherit)] - #[error(transparent)] - RedirectResolution(#[from] RedirectResolutionError), -} - -impl From for FetchCachedError { - fn from(err: FetchCachedNoFollowError) -> Self { - match err.into_kind() { - FetchCachedNoFollowErrorKind::ChecksumIntegrity(err) => err.into(), - FetchCachedNoFollowErrorKind::CacheRead(err) => err.into(), - FetchCachedNoFollowErrorKind::RedirectResolution(err) => err.into(), - } - } -} - -impl From for FetchNoFollowError { - fn from(err: FetchCachedNoFollowError) -> Self { - match err.into_kind() { - FetchCachedNoFollowErrorKind::ChecksumIntegrity(err) => err.into(), - FetchCachedNoFollowErrorKind::CacheRead(err) => err.into(), - FetchCachedNoFollowErrorKind::RedirectResolution(err) => err.into(), - } - } -} - -#[async_trait::async_trait(?Send)] -pub trait HttpClient: std::fmt::Debug + MaybeSend + MaybeSync { - /// Send a request getting the response. - /// - /// The implementation MUST not follow redirects. Return `SendResponse::Redirect` - /// in that case. - /// - /// The implementation may retry the request on failure. - async fn send_no_follow( - &self, - url: &Url, - headers: HeaderMap, - ) -> Result; -} - -#[derive(Debug, Clone)] -pub struct BlobData { - pub media_type: String, - pub bytes: Vec, -} - -#[derive(Debug, Clone, Default)] -pub struct NullBlobStore; - -#[async_trait::async_trait(?Send)] -impl BlobStore for NullBlobStore { - async fn get(&self, _url: &Url) -> std::io::Result> { - Ok(None) - } -} - -#[async_trait::async_trait(?Send)] -pub trait BlobStore: std::fmt::Debug + MaybeSend + MaybeSync { - async fn get(&self, url: &Url) -> std::io::Result>; -} - -#[derive(Debug, Default)] -pub struct FetchNoFollowOptions<'a> { - pub local: FetchLocalOptions, - pub maybe_auth: Option<(header::HeaderName, header::HeaderValue)>, - pub maybe_checksum: Option>, - pub maybe_accept: Option<&'a str>, - pub maybe_cache_setting: Option<&'a CacheSetting>, -} - -#[derive(Debug, Clone, Default)] -pub struct FetchLocalOptions { - pub include_mtime: bool, -} - -#[derive(Debug)] -pub struct FileFetcherOptions { - pub allow_remote: bool, - pub cache_setting: CacheSetting, - pub auth_tokens: AuthTokens, -} - -#[sys_traits::auto_impl] -pub trait FileFetcherSys: FsOpen + SystemTimeNow {} - -/// A structure for resolving, fetching and caching source files. -#[derive(Debug)] -pub struct FileFetcher< - TBlobStore: BlobStore, - TSys: FileFetcherSys, - THttpClient: HttpClient, -> { - blob_store: TBlobStore, - sys: TSys, - http_cache: HttpCacheRc, - http_client: THttpClient, - memory_files: MemoryFilesRc, - allow_remote: bool, - cache_setting: CacheSetting, - auth_tokens: AuthTokens, -} - -impl - FileFetcher -{ - pub fn new( - blob_store: TBlobStore, - sys: TSys, - http_cache: HttpCacheRc, - http_client: THttpClient, - memory_files: MemoryFilesRc, - options: FileFetcherOptions, - ) -> Self { - Self { - blob_store, - sys, - http_cache, - http_client, - memory_files, - allow_remote: options.allow_remote, - auth_tokens: options.auth_tokens, - cache_setting: options.cache_setting, - } - } - - pub fn cache_setting(&self) -> &CacheSetting { - &self.cache_setting - } - - /// Fetch cached remote file. - pub fn fetch_cached( - &self, - url: &Url, - redirect_limit: i64, - ) -> Result, FetchCachedError> { - if !matches!(url.scheme(), "http" | "https") { - return Ok(None); - } - - let mut url = Cow::Borrowed(url); - for _ in 0..=redirect_limit { - match self.fetch_cached_no_follow(&url, None)? { - Some(FileOrRedirect::File(file)) => { - return Ok(Some(file)); - } - Some(FileOrRedirect::Redirect(redirect_url)) => { - url = Cow::Owned(redirect_url); - } - None => { - return Ok(None); - } - } - } - Err( - FetchCachedErrorKind::TooManyRedirects(TooManyRedirectsError( - url.into_owned(), - )) - .into_box(), - ) - } - - /// Fetches without following redirects. - /// - /// You should verify permissions of the specifier before calling this function. - pub async fn fetch_no_follow( - &self, - url: &Url, - options: FetchNoFollowOptions<'_>, - ) -> Result { - // note: this debug output is used by the tests - debug!("FileFetcher::fetch_no_follow - specifier: {}", url); - self - .fetch_no_follow_with_strategy(&FetchStrategy(self), url, options) - .await - } - - /// Ensures the data is cached without following redirects. - /// - /// You should verify permissions of the specifier before calling this function. - pub async fn ensure_cached_no_follow( - &self, - url: &Url, - options: FetchNoFollowOptions<'_>, - ) -> Result { - // note: this debug output is used by the tests - debug!("FileFetcher::ensure_cached_no_follow - specifier: {}", url); - self - .fetch_no_follow_with_strategy(&EnsureCachedStrategy(self), url, options) - .await - } - - async fn fetch_no_follow_with_strategy< - TStrategy: FetchOrEnsureCacheStrategy, - >( - &self, - strategy: &TStrategy, - url: &Url, - options: FetchNoFollowOptions<'_>, - ) -> Result { - let scheme = url.scheme(); - if let Some(file) = self.memory_files.get(url) { - Ok(strategy.handle_memory_file(file)) - } else if scheme == "file" { - match strategy.handle_local(url, &options.local)? { - Some(file) => Ok(file), - None => Err(FetchNoFollowErrorKind::NotFound(url.clone()).into_box()), - } - } else if scheme == "data" { - strategy - .handle_data_url(url) - .map_err(|e| FetchNoFollowErrorKind::DataUrlDecode(e).into_box()) - } else if scheme == "blob" { - strategy.handle_blob_url(url).await - } else if scheme == "https" || scheme == "http" { - if !self.allow_remote { - Err(FetchNoFollowErrorKind::NoRemote(url.clone()).into_box()) - } else { - self - .fetch_remote_no_follow( - strategy, - url, - options.maybe_accept, - options.maybe_cache_setting.unwrap_or(&self.cache_setting), - options.maybe_checksum, - options.maybe_auth, - ) - .await - } - } else { - Err( - FetchNoFollowErrorKind::UnsupportedScheme(UnsupportedSchemeError { - scheme: scheme.to_string(), - url: url.clone(), - }) - .into_box(), - ) - } - } - - fn fetch_cached_no_follow( - &self, - url: &Url, - maybe_checksum: Option>, - ) -> Result, FetchCachedNoFollowError> { - debug!("FileFetcher::fetch_cached_no_follow - specifier: {}", url); - - let cache_key = - self - .http_cache - .cache_item_key(url) - .map_err(|source| CacheReadError { - url: url.clone(), - source, - })?; - match self.http_cache.get(&cache_key, maybe_checksum) { - Ok(Some(entry)) => { - Ok(Some(FileOrRedirect::from_deno_cache_entry(url, entry)?)) - } - Ok(None) => Ok(None), - Err(CacheReadFileError::Io(source)) => Err( - FetchCachedNoFollowErrorKind::CacheRead(CacheReadError { - url: url.clone(), - source, - }) - .into_box(), - ), - Err(CacheReadFileError::ChecksumIntegrity(err)) => { - Err(FetchCachedNoFollowErrorKind::ChecksumIntegrity(*err).into_box()) - } - } - } - - /// Convert a data URL into a file, resulting in an error if the URL is - /// invalid. - fn fetch_data_url(&self, url: &Url) -> Result { - fn parse( - url: &Url, - ) -> Result<(Vec, HashMap), DataUrlDecodeError> { - let url = DataUrl::process(url.as_str()).map_err(|source| { - DataUrlDecodeError { - source: DataUrlDecodeSourceError::DataUrl(source), - } - })?; - let (bytes, _) = - url.decode_to_vec().map_err(|source| DataUrlDecodeError { - source: DataUrlDecodeSourceError::InvalidBase64(source), - })?; - let headers = HashMap::from([( - "content-type".to_string(), - url.mime_type().to_string(), - )]); - Ok((bytes, headers)) - } - - debug!("FileFetcher::fetch_data_url() - specifier: {}", url); - let (bytes, headers) = parse(url)?; - Ok(File { - url: url.clone(), - mtime: None, - maybe_headers: Some(headers), - loaded_from: LoadedFrom::Local, - #[allow(clippy::disallowed_types)] // ok for source - source: std::sync::Arc::from(bytes), - }) - } - - /// Get a blob URL. - async fn fetch_blob_url( - &self, - url: &Url, - ) -> Result { - debug!("FileFetcher::fetch_blob_url() - specifier: {}", url); - let blob = self - .blob_store - .get(url) - .await - .map_err(|err| FetchNoFollowErrorKind::ReadingBlobUrl { - url: url.clone(), - source: err, - })? - .ok_or_else(|| FetchNoFollowErrorKind::NotFound(url.clone()))?; - - let headers = - HashMap::from([("content-type".to_string(), blob.media_type.clone())]); - - Ok(File { - url: url.clone(), - mtime: None, - maybe_headers: Some(headers), - loaded_from: LoadedFrom::Local, - #[allow(clippy::disallowed_types)] // ok for source - source: std::sync::Arc::from(blob.bytes), - }) - } - - async fn fetch_remote_no_follow( - &self, - strategy: &TStrategy, - url: &Url, - maybe_accept: Option<&str>, - cache_setting: &CacheSetting, - maybe_checksum: Option>, - maybe_auth: Option<(header::HeaderName, header::HeaderValue)>, - ) -> Result { - debug!("FileFetcher::fetch_remote_no_follow - specifier: {}", url); - - if self.should_use_cache(url, cache_setting) - && let Some(value) = - strategy.handle_fetch_cached_no_follow(url, maybe_checksum)? - { - return Ok(value); - } - - if *cache_setting == CacheSetting::Only { - return Err( - FetchNoFollowErrorKind::NotCached { url: url.clone() }.into_box(), - ); - } - - strategy - .handle_fetch_remote_no_follow_no_cache( - url, - maybe_accept, - maybe_checksum, - maybe_auth, - ) - .await - } - - async fn fetch_remote_no_follow_no_cache( - &self, - url: &Url, - maybe_accept: Option<&str>, - maybe_checksum: Option>, - maybe_auth: Option<(header::HeaderName, header::HeaderValue)>, - ) -> Result { - let maybe_etag_cache_entry = self - .http_cache - .cache_item_key(url) - .ok() - .and_then(|key| self.http_cache.get(&key, maybe_checksum).ok().flatten()) - .and_then(|mut cache_entry| { - cache_entry - .metadata - .headers - .remove("etag") - .map(|etag| (cache_entry, etag)) - }); - - let maybe_auth_token = self.auth_tokens.get(url); - match self - .send_request(SendRequestArgs { - url, - maybe_accept, - maybe_auth: maybe_auth.clone(), - maybe_auth_token, - maybe_etag: maybe_etag_cache_entry - .as_ref() - .map(|(_, etag)| etag.as_str()), - }) - .await? - { - SendRequestResponse::NotModified => { - let (cache_entry, _) = maybe_etag_cache_entry.unwrap(); - FileOrRedirect::from_deno_cache_entry(url, cache_entry).map_err(|err| { - FetchNoFollowErrorKind::RedirectResolution(err).into_box() - }) - } - SendRequestResponse::Redirect(redirect_url, headers) => { - self.http_cache.set(url, headers, &[]).map_err(|source| { - FetchNoFollowErrorKind::CacheSave { - url: url.clone(), - source, - } - })?; - Ok(FileOrRedirect::Redirect(redirect_url)) - } - SendRequestResponse::Code(bytes, headers) => { - self.http_cache.set(url, headers.clone(), &bytes).map_err( - |source| FetchNoFollowErrorKind::CacheSave { - url: url.clone(), - source, - }, - )?; - if let Some(checksum) = &maybe_checksum { - checksum - .check(url, &bytes) - .map_err(|err| FetchNoFollowErrorKind::ChecksumIntegrity(*err))?; - } - Ok(FileOrRedirect::File(File { - url: url.clone(), - mtime: None, - maybe_headers: Some(headers), - #[allow(clippy::disallowed_types)] // ok for source - source: std::sync::Arc::from(bytes), - loaded_from: LoadedFrom::Remote, - })) - } - } - } - - /// Returns if the cache should be used for a given specifier. - fn should_use_cache(&self, url: &Url, cache_setting: &CacheSetting) -> bool { - match cache_setting { - CacheSetting::ReloadAll => false, - CacheSetting::Use | CacheSetting::Only => true, - CacheSetting::RespectHeaders => { - let Ok(cache_key) = self.http_cache.cache_item_key(url) else { - return false; - }; - let Ok(Some(headers)) = self.http_cache.read_headers(&cache_key) else { - return false; - }; - let Ok(Some(download_time)) = - self.http_cache.read_download_time(&cache_key) - else { - return false; - }; - let cache_semantics = - CacheSemantics::new(headers, download_time, self.sys.sys_time_now()); - cache_semantics.should_use() - } - CacheSetting::ReloadSome(list) => { - let mut url = url.clone(); - url.set_fragment(None); - if list.iter().any(|x| x == url.as_str()) { - return false; - } - url.set_query(None); - let mut path = PathBuf::from(url.as_str()); - loop { - if list.contains(&path.to_str().unwrap().to_string()) { - return false; - } - if !path.pop() { - break; - } - } - true - } - } - } - - /// Asynchronously fetches the given HTTP URL one pass only. - /// If no redirect is present and no error occurs, - /// yields Code(ResultPayload). - /// If redirect occurs, does not follow and - /// yields Redirect(url). - async fn send_request( - &self, - args: SendRequestArgs<'_>, - ) -> Result { - let mut headers = HeaderMap::with_capacity(3); - - if let Some(etag) = args.maybe_etag { - let if_none_match_val = - HeaderValue::from_str(etag).map_err(|source| { - FetchNoFollowErrorKind::InvalidHeader { - name: "etag", - source, - } - })?; - headers.insert(IF_NONE_MATCH, if_none_match_val); - } - if let Some(auth_token) = args.maybe_auth_token { - let authorization_val = HeaderValue::from_str(&auth_token.to_string()) - .map_err(|source| FetchNoFollowErrorKind::InvalidHeader { - name: "authorization", - source, - })?; - headers.insert(AUTHORIZATION, authorization_val); - } else if let Some((header, value)) = args.maybe_auth { - headers.insert(header, value); - } - if let Some(accept) = args.maybe_accept { - let accepts_val = HeaderValue::from_str(accept).map_err(|source| { - FetchNoFollowErrorKind::InvalidHeader { - name: "accept", - source, - } - })?; - headers.insert(ACCEPT, accepts_val); - } - match self.http_client.send_no_follow(args.url, headers).await { - Ok(resp) => match resp { - SendResponse::NotModified => Ok(SendRequestResponse::NotModified), - SendResponse::Redirect(headers) => { - let new_url = resolve_redirect_from_headers(args.url, &headers) - .map_err(|err| { - FetchNoFollowErrorKind::RedirectHeaderParse(*err).into_box() - })?; - Ok(SendRequestResponse::Redirect( - new_url, - response_headers_to_headers_map(headers), - )) - } - SendResponse::Success(headers, body) => Ok(SendRequestResponse::Code( - body, - response_headers_to_headers_map(headers), - )), - }, - Err(err) => match err { - SendError::Failed(err) => Err( - FetchNoFollowErrorKind::FetchingRemote { - url: args.url.clone(), - source: err, - } - .into_box(), - ), - SendError::NotFound => { - Err(FetchNoFollowErrorKind::NotFound(args.url.clone()).into_box()) - } - SendError::StatusCode(status_code) => Err( - FetchNoFollowErrorKind::ClientError { - url: args.url.clone(), - status_code, - } - .into_box(), - ), - }, - } - } - - /// Fetch a source file from the local file system. - pub fn fetch_local( - &self, - url: &Url, - options: &FetchLocalOptions, - ) -> Result, FetchLocalError> { - let local = url_to_file_path(url)?; - let Some(file) = self.handle_open_file(url, &local)? else { - return Ok(None); - }; - match self.fetch_local_inner(file, url, &local, options) { - Ok(file) => Ok(Some(file)), - Err(err) => Err( - FetchLocalErrorKind::ReadingFile(FailedReadingLocalFileError { - url: url.clone(), - source: err, - }) - .into_box(), - ), - } - } - - fn handle_open_file( - &self, - url: &Url, - path: &Path, - ) -> Result, FetchLocalError> { - match self.sys.fs_open(path, &OpenOptions::new_read()) { - Ok(file) => Ok(Some(file)), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), - Err(err) => Err( - FetchLocalErrorKind::ReadingFile(FailedReadingLocalFileError { - url: url.clone(), - source: err, - }) - .into_box(), - ), - } - } - - fn fetch_local_inner( - &self, - mut file: TSys::File, - url: &Url, - path: &Path, - options: &FetchLocalOptions, - ) -> std::io::Result { - let mtime = if options.include_mtime { - file.fs_file_metadata().and_then(|m| m.modified()).ok() - } else { - None - }; - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes)?; - // If it doesnt have a extension, we want to treat it as typescript by default - let headers = if path.extension().is_none() { - Some(HashMap::from([( - "content-type".to_string(), - "application/typescript".to_string(), - )])) - } else { - None - }; - Ok(File { - url: url.clone(), - mtime, - maybe_headers: headers, - loaded_from: LoadedFrom::Local, - source: bytes.into(), - }) - } -} - -#[async_trait::async_trait(?Send)] -trait FetchOrEnsureCacheStrategy { - type ReturnValue; - - fn handle_memory_file(&self, file: File) -> Self::ReturnValue; - - fn handle_local( - &self, - url: &Url, - options: &FetchLocalOptions, - ) -> Result, FetchLocalError>; - - fn handle_data_url( - &self, - url: &Url, - ) -> Result; - - async fn handle_blob_url( - &self, - url: &Url, - ) -> Result; - - fn handle_fetch_cached_no_follow( - &self, - url: &Url, - maybe_checksum: Option>, - ) -> Result, FetchCachedNoFollowError>; - - async fn handle_fetch_remote_no_follow_no_cache( - &self, - url: &Url, - maybe_accept: Option<&str>, - maybe_checksum: Option>, - maybe_auth: Option<(header::HeaderName, header::HeaderValue)>, - ) -> Result; -} - -struct FetchStrategy< - 'a, - TBlobStore: BlobStore, - TSys: FileFetcherSys, - THttpClient: HttpClient, ->(&'a FileFetcher); - -#[async_trait::async_trait(?Send)] -impl - FetchOrEnsureCacheStrategy - for FetchStrategy<'_, TBlobStore, TSys, THttpClient> -{ - type ReturnValue = FileOrRedirect; - - fn handle_memory_file(&self, file: File) -> FileOrRedirect { - FileOrRedirect::File(file) - } - - fn handle_local( - &self, - url: &Url, - options: &FetchLocalOptions, - ) -> Result, FetchLocalError> { - self - .0 - .fetch_local(url, options) - .map(|maybe_value| maybe_value.map(FileOrRedirect::File)) - } - - fn handle_data_url( - &self, - url: &Url, - ) -> Result { - self.0.fetch_data_url(url).map(FileOrRedirect::File) - } - - async fn handle_blob_url( - &self, - url: &Url, - ) -> Result { - self.0.fetch_blob_url(url).await.map(FileOrRedirect::File) - } - - fn handle_fetch_cached_no_follow( - &self, - url: &Url, - maybe_checksum: Option>, - ) -> Result, FetchCachedNoFollowError> { - self.0.fetch_cached_no_follow(url, maybe_checksum) - } - - async fn handle_fetch_remote_no_follow_no_cache( - &self, - url: &Url, - maybe_accept: Option<&str>, - maybe_checksum: Option>, - maybe_auth: Option<(header::HeaderName, header::HeaderValue)>, - ) -> Result { - self - .0 - .fetch_remote_no_follow_no_cache( - url, - maybe_accept, - maybe_checksum, - maybe_auth, - ) - .await - } -} - -struct EnsureCachedStrategy< - 'a, - TBlobStore: BlobStore, - TSys: FileFetcherSys, - THttpClient: HttpClient, ->(&'a FileFetcher); - -#[async_trait::async_trait(?Send)] -impl - FetchOrEnsureCacheStrategy - for EnsureCachedStrategy<'_, TBlobStore, TSys, THttpClient> -{ - type ReturnValue = CachedOrRedirect; - - fn handle_memory_file(&self, _file: File) -> CachedOrRedirect { - CachedOrRedirect::Cached - } - - fn handle_local( - &self, - url: &Url, - _options: &FetchLocalOptions, - ) -> Result, FetchLocalError> { - let path = url_to_file_path(url)?; - let maybe_file = self.0.handle_open_file(url, &path)?; - Ok(maybe_file.map(|_| CachedOrRedirect::Cached)) - } - - fn handle_data_url( - &self, - _url: &Url, - ) -> Result { - Ok(CachedOrRedirect::Cached) - } - - async fn handle_blob_url( - &self, - _url: &Url, - ) -> Result { - Ok(CachedOrRedirect::Cached) - } - - fn handle_fetch_cached_no_follow( - &self, - url: &Url, - _maybe_checksum: Option>, - ) -> Result, FetchCachedNoFollowError> { - // We don't take into account the checksum here because we assume - // the bytes were verified when initially downloading the data - // from the remote server. This is to prevent loading the data into - // memory. - if self.0.http_cache.contains(url) { - Ok(Some(CachedOrRedirect::Cached)) - } else { - Ok(None) - } - } - - async fn handle_fetch_remote_no_follow_no_cache( - &self, - url: &Url, - maybe_accept: Option<&str>, - maybe_checksum: Option>, - maybe_auth: Option<(header::HeaderName, header::HeaderValue)>, - ) -> Result { - self - .0 - .fetch_remote_no_follow_no_cache( - url, - maybe_accept, - maybe_checksum, - maybe_auth, - ) - .await - .map(|file_or_redirect| file_or_redirect.into()) - } -} - -fn response_headers_to_headers_map(response_headers: HeaderMap) -> HeadersMap { - let mut result_headers = HashMap::with_capacity(response_headers.len()); - // todo(dsherret): change to consume to avoid allocations - for key in response_headers.keys() { - let key_str = key.to_string(); - let values = response_headers.get_all(key); - // todo(dsherret): this seems very strange storing them comma separated - // like this... what happens if a value contains a comma? - let values_str = values - .iter() - .filter_map(|e| Some(e.to_str().ok()?.to_string())) - .collect::>() - .join(","); - result_headers.insert(key_str, values_str); - } - result_headers -} - -pub fn resolve_redirect_from_headers( - request_url: &Url, - headers: &HeaderMap, -) -> Result> { - if let Some(location) = headers.get(LOCATION) { - let location_string = location.to_str().map_err(|source| { - Box::new(RedirectHeaderParseError { - request_url: request_url.clone(), - maybe_location: None, - maybe_source: Some(source.into()), - }) - })?; - log::debug!("Redirecting to {:?}...", &location_string); - resolve_url_from_location(request_url, location_string).map_err(|source| { - Box::new(RedirectHeaderParseError { - request_url: request_url.clone(), - maybe_location: Some(location_string.to_string()), - maybe_source: Some(source), - }) - }) - } else { - Err(Box::new(RedirectHeaderParseError { - request_url: request_url.clone(), - maybe_location: None, - maybe_source: None, - })) - } -} - -/// Construct the next uri based on base uri and location header fragment -/// See -fn resolve_url_from_location( - base_url: &Url, - location: &str, -) -> Result> { - // todo(dsherret): these shouldn't unwrap - if location.starts_with("http://") || location.starts_with("https://") { - // absolute uri - Ok(Url::parse(location)?) - } else if location.starts_with("//") { - // "//" authority path-abempty - Ok(Url::parse(&format!("{}:{}", base_url.scheme(), location))?) - } else if location.starts_with('/') { - // path-absolute - Ok(base_url.join(location)?) - } else { - // assuming path-noscheme | path-empty - let base_url_path_str = base_url.path().to_owned(); - // Pop last part or url (after last slash) - let segs: Vec<&str> = base_url_path_str.rsplitn(2, '/').collect(); - let new_path = format!("{}/{}", segs.last().unwrap_or(&""), location); - Ok(base_url.join(&new_path)?) - } -} - -#[derive(Debug)] -struct SendRequestArgs<'a> { - pub url: &'a Url, - pub maybe_accept: Option<&'a str>, - pub maybe_etag: Option<&'a str>, - pub maybe_auth_token: Option<&'a AuthToken>, - pub maybe_auth: Option<(header::HeaderName, header::HeaderValue)>, -} - -#[derive(Debug, Eq, PartialEq)] -enum SendRequestResponse { - Code(Vec, HeadersMap), - NotModified, - Redirect(Url, HeadersMap), -} - -#[cfg(test)] -mod test { - use url::Url; - - use crate::file_fetcher::resolve_url_from_location; - - #[test] - fn test_resolve_url_from_location_full_1() { - let url = "http://deno.land".parse::().unwrap(); - let new_uri = resolve_url_from_location(&url, "http://golang.org").unwrap(); - assert_eq!(new_uri.host_str().unwrap(), "golang.org"); - } - - #[test] - fn test_resolve_url_from_location_full_2() { - let url = "https://deno.land".parse::().unwrap(); - let new_uri = - resolve_url_from_location(&url, "https://golang.org").unwrap(); - assert_eq!(new_uri.host_str().unwrap(), "golang.org"); - } - - #[test] - fn test_resolve_url_from_location_relative_1() { - let url = "http://deno.land/x".parse::().unwrap(); - let new_uri = - resolve_url_from_location(&url, "//rust-lang.org/en-US").unwrap(); - assert_eq!(new_uri.host_str().unwrap(), "rust-lang.org"); - assert_eq!(new_uri.path(), "/en-US"); - } - - #[test] - fn test_resolve_url_from_location_relative_2() { - let url = "http://deno.land/x".parse::().unwrap(); - let new_uri = resolve_url_from_location(&url, "/y").unwrap(); - assert_eq!(new_uri.host_str().unwrap(), "deno.land"); - assert_eq!(new_uri.path(), "/y"); - } - - #[test] - fn test_resolve_url_from_location_relative_3() { - let url = "http://deno.land/x".parse::().unwrap(); - let new_uri = resolve_url_from_location(&url, "z").unwrap(); - assert_eq!(new_uri.host_str().unwrap(), "deno.land"); - assert_eq!(new_uri.path(), "/z"); - } -} diff --git a/rs_lib/src/global/cache_file.rs b/rs_lib/src/global/cache_file.rs deleted file mode 100644 index 1ec7f02..0000000 --- a/rs_lib/src/global/cache_file.rs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::borrow::Cow; -use std::io::ErrorKind; -use std::path::Path; - -use deno_path_util::fs::atomic_write_file_with_retries; -use serde::de::DeserializeOwned; -use sys_traits::FsCreateDirAll; -use sys_traits::FsMetadata; -use sys_traits::FsOpen; -use sys_traits::FsRead; -use sys_traits::FsRemoveFile; -use sys_traits::FsRename; -use sys_traits::SystemRandom; -use sys_traits::ThreadSleep; - -use crate::CACHE_PERM; -use crate::SerializedCachedUrlMetadata; -use crate::cache::CacheEntry; - -// File format: -// \n// denoCacheMetadata= - -const LAST_LINE_PREFIX: &[u8] = b"\n// denoCacheMetadata="; - -pub fn write< - TSys: FsCreateDirAll - + FsMetadata - + FsOpen - + FsRemoveFile - + FsRename - + ThreadSleep - + SystemRandom, ->( - sys: &TSys, - path: &Path, - content: &[u8], - metadata: &SerializedCachedUrlMetadata, -) -> std::io::Result<()> { - fn estimate_metadata_capacity( - metadata: &SerializedCachedUrlMetadata, - ) -> usize { - metadata - .headers - .iter() - .map(|(k, v)| k.len() + v.len() + 6) - .sum::() - + metadata.url.len() - + metadata.time.as_ref().map(|_| 14).unwrap_or(0) - + 128 // overestimate - } - - let capacity = content.len() - + LAST_LINE_PREFIX.len() - + estimate_metadata_capacity(metadata); - let mut result = Vec::with_capacity(capacity); - result.extend(content); - result.extend(LAST_LINE_PREFIX); - serde_json::to_writer(&mut result, &metadata).unwrap(); - debug_assert!(result.len() < capacity, "{} < {}", result.len(), capacity); - atomic_write_file_with_retries(sys, path, &result, CACHE_PERM)?; - Ok(()) -} - -pub fn read( - sys: &impl FsRead, - path: &Path, -) -> std::io::Result> { - let original_file_bytes = match sys.fs_read(path) { - Ok(file) => file, - Err(err) if err.kind() == ErrorKind::NotFound => return Ok(None), - Err(err) => return Err(err), - }; - - let Some((content, metadata)) = - read_content_and_metadata(&original_file_bytes) - else { - return Ok(None); - }; - - let content_len = content.len(); - // truncate the original bytes to just the content - let original_file_bytes = match original_file_bytes { - Cow::Borrowed(bytes) => Cow::Borrowed(&bytes[..content_len]), - Cow::Owned(mut bytes) => { - bytes.truncate(content_len); - Cow::Owned(bytes) - } - }; - - Ok(Some(CacheEntry { - metadata, - content: original_file_bytes, - })) -} - -pub fn read_metadata( - sys: &impl FsRead, - path: &Path, -) -> std::io::Result> { - let file_bytes = match sys.fs_read(path) { - Ok(file) => file, - Err(err) if err.kind() == ErrorKind::NotFound => return Ok(None), - Err(err) => return Err(err), - }; - - let Some((_content_bytes, metadata)) = - read_content_and_metadata::(&file_bytes) - else { - return Ok(None); - }; - - Ok(Some(metadata)) -} - -fn read_content_and_metadata( - file_bytes: &[u8], -) -> Option<(&[u8], TMetadata)> { - let (file_bytes, metadata_bytes) = split_content_metadata(file_bytes)?; - let serialized_metadata = - serde_json::from_slice::(metadata_bytes).ok()?; - - Some((file_bytes, serialized_metadata)) -} - -fn split_content_metadata(file_bytes: &[u8]) -> Option<(&[u8], &[u8])> { - let last_newline_index = file_bytes.iter().rposition(|&b| b == b'\n')?; - - let (content, trailing_bytes) = file_bytes.split_at(last_newline_index); - let metadata = trailing_bytes.strip_prefix(LAST_LINE_PREFIX)?; - Some((content, metadata)) -} diff --git a/rs_lib/src/global/mod.rs b/rs_lib/src/global/mod.rs deleted file mode 100644 index e9e561a..0000000 --- a/rs_lib/src/global/mod.rs +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::path::PathBuf; -use std::time::Duration; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; - -use serde::Deserialize; -use sys_traits::FsCreateDirAll; -use sys_traits::FsMetadata; -use sys_traits::FsMetadataValue; -use sys_traits::FsOpen; -use sys_traits::FsRead; -use sys_traits::FsRemoveFile; -use sys_traits::FsRename; -use sys_traits::SystemRandom; -use sys_traits::SystemTimeNow; -use sys_traits::ThreadSleep; -use url::Url; - -use super::cache::HttpCache; -use super::cache::HttpCacheItemKey; -use crate::cache::CacheEntry; -use crate::cache::CacheReadFileError; -use crate::cache::Checksum; -use crate::cache::SerializedCachedUrlMetadata; -use crate::cache::url_to_filename; -use crate::common::HeadersMap; -use crate::sync::MaybeSend; -use crate::sync::MaybeSync; - -mod cache_file; - -#[sys_traits::auto_impl] -pub trait GlobalHttpCacheSys: - FsCreateDirAll - + FsMetadata - + FsOpen - + FsRead - + FsRemoveFile - + FsRename - + ThreadSleep - + SystemRandom - + SystemTimeNow - + std::fmt::Debug - + MaybeSend - + MaybeSync - + Clone -{ -} - -#[allow(clippy::disallowed_types)] -pub type GlobalHttpCacheRc = crate::sync::MaybeArc>; - -#[derive(Debug)] -pub struct GlobalHttpCache { - path: PathBuf, - pub(crate) sys: Sys, -} - -impl GlobalHttpCache { - pub fn new(sys: Sys, path: PathBuf) -> Self { - #[cfg(not(target_arch = "wasm32"))] - assert!(path.is_absolute()); - Self { path, sys } - } - - pub fn dir_path(&self) -> &PathBuf { - &self.path - } - - pub fn local_path_for_url(&self, url: &Url) -> std::io::Result { - Ok(self.path.join(url_to_filename(url)?)) - } - - #[inline] - fn key_file_path<'a>(&self, key: &'a HttpCacheItemKey) -> &'a PathBuf { - // The key file path is always set for the global cache because - // the file will always exist, unlike the local cache, which won't - // have this for redirects. - key.file_path.as_ref().unwrap() - } -} - -impl HttpCache for GlobalHttpCache { - fn cache_item_key<'a>( - &self, - url: &'a Url, - ) -> std::io::Result> { - Ok(HttpCacheItemKey { - #[cfg(debug_assertions)] - is_local_key: false, - url, - file_path: Some(self.local_path_for_url(url)?), - }) - } - - fn contains(&self, url: &Url) -> bool { - let Ok(cache_filepath) = self.local_path_for_url(url) else { - return false; - }; - self.sys.fs_is_file(&cache_filepath).unwrap_or(false) - } - - fn read_modified_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - #[cfg(debug_assertions)] - debug_assert!(!key.is_local_key); - - match self.sys.fs_metadata(self.key_file_path(key)) { - Ok(metadata) => match metadata.modified() { - Ok(time) => Ok(Some(time)), - Err(_) => Ok(Some(self.sys.sys_time_now())), - }, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), - Err(err) => Err(err), - } - } - - fn set( - &self, - url: &Url, - headers: HeadersMap, - content: &[u8], - ) -> std::io::Result<()> { - let cache_filepath = self.local_path_for_url(url)?; - cache_file::write( - &self.sys, - &cache_filepath, - content, - &SerializedCachedUrlMetadata { - time: Some( - self - .sys - .sys_time_now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - ), - url: url.to_string(), - headers, - }, - )?; - - Ok(()) - } - - fn get( - &self, - key: &HttpCacheItemKey, - maybe_checksum: Option, - ) -> Result, CacheReadFileError> { - #[cfg(debug_assertions)] - debug_assert!(!key.is_local_key); - - let maybe_file = cache_file::read(&self.sys, self.key_file_path(key))?; - - if let Some(file) = &maybe_file - && let Some(expected_checksum) = maybe_checksum - { - expected_checksum - .check(key.url, &file.content) - .map_err(CacheReadFileError::ChecksumIntegrity)?; - } - - Ok(maybe_file) - } - - fn read_headers( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - // targeted deserialize - #[derive(Deserialize)] - struct SerializedHeaders { - pub headers: HeadersMap, - } - - #[cfg(debug_assertions)] - debug_assert!(!key.is_local_key); - - let maybe_metadata = cache_file::read_metadata::( - &self.sys, - self.key_file_path(key), - )?; - Ok(maybe_metadata.map(|m| m.headers)) - } - - fn read_download_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - // targeted deserialize - #[derive(Deserialize)] - struct SerializedTime { - pub time: Option, - } - - #[cfg(debug_assertions)] - debug_assert!(!key.is_local_key); - let maybe_metadata = cache_file::read_metadata::( - &self.sys, - self.key_file_path(key), - )?; - Ok(maybe_metadata.and_then(|m| { - Some(SystemTime::UNIX_EPOCH + Duration::from_secs(m.time?)) - })) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_url_to_filename() { - let test_cases = [ - ( - "https://deno.land/x/foo.ts", - "https/deno.land/2c0a064891b9e3fbe386f5d4a833bce5076543f5404613656042107213a7bbc8", - ), - ( - "https://deno.land:8080/x/foo.ts", - "https/deno.land_PORT8080/2c0a064891b9e3fbe386f5d4a833bce5076543f5404613656042107213a7bbc8", - ), - ( - "https://deno.land/", - "https/deno.land/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1", - ), - ( - "https://deno.land/?asdf=qwer", - "https/deno.land/e4edd1f433165141015db6a823094e6bd8f24dd16fe33f2abd99d34a0a21a3c0", - ), - // should be the same as case above, fragment (#qwer) is ignored - // when hashing - ( - "https://deno.land/?asdf=qwer#qwer", - "https/deno.land/e4edd1f433165141015db6a823094e6bd8f24dd16fe33f2abd99d34a0a21a3c0", - ), - ( - "data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=", - "data/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37", - ), - ( - "data:text/plain,Hello%2C%20Deno!", - "data/967374e3561d6741234131e342bf5c6848b70b13758adfe23ee1a813a8131818", - ), - ]; - - for (url, expected) in test_cases.iter() { - let u = Url::parse(url).unwrap(); - let p = url_to_filename(&u).unwrap(); - assert_eq!(p, PathBuf::from(expected)); - } - } -} diff --git a/rs_lib/src/lib.rs b/rs_lib/src/lib.rs deleted file mode 100644 index 9b45b2c..0000000 --- a/rs_lib/src/lib.rs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -mod cache; -mod common; -mod deno_dir; -#[cfg(feature = "file_fetcher")] -pub mod file_fetcher; -mod global; -mod local; -pub mod memory; -pub mod npm; -mod sync; - -/// Permissions used to save a file in the disk caches. -pub const CACHE_PERM: u32 = 0o644; - -pub use cache::CacheEntry; -pub use cache::CacheReadFileError; -pub use cache::Checksum; -pub use cache::ChecksumIntegrityError; -pub use cache::GlobalOrLocalHttpCache; -pub use cache::GlobalToLocalCopy; -pub use cache::HttpCache; -pub use cache::HttpCacheItemKey; -pub use cache::HttpCacheRc; -pub use cache::SerializedCachedUrlMetadata; -pub use cache::url_to_filename; -pub use common::HeadersMap; -pub use deno_dir::DenoDirResolutionError; -pub use deno_dir::ResolveDenoDirSys; -pub use deno_dir::resolve_deno_dir; -pub use global::GlobalHttpCache; -pub use global::GlobalHttpCacheRc; -pub use global::GlobalHttpCacheSys; -pub use local::LocalHttpCache; -pub use local::LocalHttpCacheRc; -pub use local::LocalHttpCacheSys; -pub use local::LocalLspHttpCache; - -#[cfg(feature = "wasm")] -pub mod wasm { - use std::collections::HashMap; - use std::io::ErrorKind; - use std::path::PathBuf; - - use js_sys::Object; - use js_sys::Reflect; - use js_sys::Uint8Array; - use sys_traits::EnvVar; - use sys_traits::impls::RealSys; - use sys_traits::impls::wasm_path_to_str; - use sys_traits::impls::wasm_string_to_path; - use url::Url; - use wasm_bindgen::prelude::*; - - use crate::CacheReadFileError; - use crate::Checksum; - use crate::HttpCache; - use crate::cache::CacheEntry; - use crate::cache::GlobalToLocalCopy; - use crate::common::HeadersMap; - use crate::deno_dir; - use crate::sync::new_rc; - - #[wasm_bindgen] - pub fn url_to_filename(url: &str) -> Result { - console_error_panic_hook::set_once(); - let url = parse_url(url).map_err(as_js_error)?; - crate::cache::url_to_filename(&url) - .map(|s| s.to_string_lossy().to_string()) - .map_err(as_js_error) - } - - #[wasm_bindgen] - pub fn resolve_deno_dir( - maybe_custom_root: Option, - ) -> Result { - console_error_panic_hook::set_once(); - deno_dir::resolve_deno_dir( - &RealSys, - maybe_custom_root.map(wasm_string_to_path), - ) - .map(|path| wasm_path_to_str(&path).into_owned()) - .map_err(|e| JsValue::from(js_sys::Error::new(&e.to_string()))) - } - - #[wasm_bindgen] - pub struct GlobalHttpCache { - cache: crate::GlobalHttpCache, - } - - #[wasm_bindgen] - impl GlobalHttpCache { - pub fn new(path: &str) -> Self { - Self { - cache: crate::GlobalHttpCache::new(RealSys, PathBuf::from(path)), - } - } - - #[wasm_bindgen(js_name = getHeaders)] - pub fn get_headers(&self, url: &str) -> Result { - get_headers(&self.cache, url) - } - - pub fn get( - &self, - url: &str, - maybe_checksum: Option, - ) -> Result { - get_cache_entry(&self.cache, url, maybe_checksum.as_deref()) - } - - pub fn set( - &self, - url: &str, - headers: JsValue, - text: &[u8], - ) -> Result<(), JsValue> { - set(&self.cache, url, headers, text) - } - } - - #[wasm_bindgen] - pub struct LocalHttpCache { - cache: crate::LocalHttpCache, - } - - #[wasm_bindgen] - impl LocalHttpCache { - pub fn new( - local_path: String, - global_path: String, - allow_global_to_local_copy: bool, - ) -> Self { - console_error_panic_hook::set_once(); - let global = - crate::GlobalHttpCache::new(RealSys, wasm_string_to_path(global_path)); - let jsr_url = RealSys - .env_var("JSR_URL") - .ok() - .and_then(|url| { - // ensure there is a trailing slash for the directory - let registry_url = format!("{}/", url.trim_end_matches('/')); - Url::parse(®istry_url).ok() - }) - .unwrap_or_else(|| Url::parse("https://jsr.io/").unwrap()); - let local = crate::LocalHttpCache::new( - wasm_string_to_path(local_path), - new_rc(global), - if allow_global_to_local_copy { - GlobalToLocalCopy::Allow - } else { - GlobalToLocalCopy::Disallow - }, - jsr_url, - ); - Self { cache: local } - } - - #[wasm_bindgen(js_name = getHeaders)] - pub fn get_headers(&self, url: &str) -> Result { - get_headers(&self.cache, url) - } - - pub fn get( - &self, - url: &str, - maybe_checksum: Option, - ) -> Result { - get_cache_entry(&self.cache, url, maybe_checksum.as_deref()) - } - - pub fn set( - &self, - url: &str, - headers: JsValue, - text: &[u8], - ) -> Result<(), JsValue> { - set(&self.cache, url, headers, text) - } - } - - fn get_headers( - cache: &Cache, - url: &str, - ) -> Result { - fn inner( - cache: &Cache, - url: &str, - ) -> std::io::Result> { - let url = parse_url(url)?; - let key = cache.cache_item_key(&url)?; - cache.read_headers(&key) - } - - inner(cache, url) - .map(|headers| match headers { - Some(headers) => serde_wasm_bindgen::to_value(&headers).unwrap(), - None => JsValue::undefined(), - }) - .map_err(as_js_error) - } - - fn get_cache_entry( - cache: &Cache, - url: &str, - maybe_checksum: Option<&str>, - ) -> Result { - fn inner( - cache: &Cache, - url: &str, - maybe_checksum: Option, - ) -> std::io::Result> { - let url = parse_url(url)?; - let key = cache.cache_item_key(&url)?; - match cache.get(&key, maybe_checksum) { - Ok(Some(entry)) => Ok(Some(entry)), - Ok(None) => Ok(None), - Err(err) => match err { - CacheReadFileError::Io(err) => Err(err), - CacheReadFileError::ChecksumIntegrity(err) => { - Err(std::io::Error::new(ErrorKind::InvalidData, err.to_string())) - } - }, - } - } - - inner(cache, url, maybe_checksum.map(Checksum::new)) - .map(|text| match text { - Some(entry) => { - let content = { - let array = Uint8Array::new_with_length(entry.content.len() as u32); - array.copy_from(&entry.content); - JsValue::from(array) - }; - let headers: JsValue = { - // make it an object instead of a Map - let headers_object = Object::new(); - for (key, value) in &entry.metadata.headers { - Reflect::set( - &headers_object, - &JsValue::from_str(key), - &JsValue::from_str(value), - ) - .unwrap(); - } - JsValue::from(headers_object) - }; - let obj = Object::new(); - Reflect::set(&obj, &JsValue::from_str("content"), &content).unwrap(); - Reflect::set(&obj, &JsValue::from_str("headers"), &headers).unwrap(); - JsValue::from(obj) - } - None => JsValue::undefined(), - }) - .map_err(as_js_error) - } - - fn set( - cache: &Cache, - url: &str, - headers: JsValue, - content: &[u8], - ) -> Result<(), JsValue> { - fn inner( - cache: &Cache, - url: &str, - headers: JsValue, - content: &[u8], - ) -> std::io::Result<()> { - let url = parse_url(url)?; - let headers: HashMap = - serde_wasm_bindgen::from_value(headers).map_err(|err| { - std::io::Error::new(ErrorKind::InvalidData, err.to_string()) - })?; - cache.set(&url, headers, content) - } - - inner(cache, url, headers, content).map_err(as_js_error) - } - - fn parse_url(url: &str) -> std::io::Result { - Url::parse(url) - .map_err(|e| std::io::Error::new(ErrorKind::InvalidInput, e.to_string())) - } - - fn as_js_error(e: std::io::Error) -> JsValue { - JsValue::from(js_sys::Error::new(&e.to_string())) - } -} diff --git a/rs_lib/src/local.rs b/rs_lib/src/local.rs deleted file mode 100644 index cf403b1..0000000 --- a/rs_lib/src/local.rs +++ /dev/null @@ -1,1371 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::borrow::Cow; -use std::collections::BTreeMap; -use std::collections::HashMap; -use std::collections::HashSet; -use std::io::ErrorKind; -use std::path::Path; -use std::path::PathBuf; -use std::time::SystemTime; - -use deno_media_type::MediaType; -use deno_path_util::fs::atomic_write_file_with_retries; -use once_cell::sync::Lazy; -use parking_lot::RwLock; -use sys_traits::FsCreateDirAll; -use sys_traits::FsMetadata; -use sys_traits::FsMetadataValue; -use sys_traits::FsOpen; -use sys_traits::FsRead; -use sys_traits::FsRemoveFile; -use sys_traits::FsRename; -use sys_traits::SystemRandom; -use sys_traits::SystemTimeNow; -use sys_traits::ThreadSleep; -use url::Url; - -use crate::CACHE_PERM; -use crate::SerializedCachedUrlMetadata; -use crate::cache::CacheEntry; -use crate::cache::CacheReadFileError; -use crate::cache::GlobalToLocalCopy; -use crate::global::GlobalHttpCacheRc; -use crate::sync::MaybeSend; -use crate::sync::MaybeSync; - -use super::Checksum; -use super::HttpCache; -use super::HttpCacheItemKey; -use super::common::HeadersMap; -use super::common::base_url_to_filename_parts; -use super::common::checksum; - -#[sys_traits::auto_impl] -pub trait LocalHttpCacheSys: - FsCreateDirAll - + FsMetadata - + FsOpen - + FsRead - + FsRemoveFile - + FsRename - + ThreadSleep - + SystemRandom - + SystemTimeNow - + MaybeSend - + MaybeSync - + std::fmt::Debug - + Clone -{ -} - -/// A vendor/ folder http cache for the lsp that provides functionality -/// for doing a reverse mapping. -#[derive(Debug)] -pub struct LocalLspHttpCache { - cache: LocalHttpCache, -} - -impl LocalLspHttpCache { - pub fn new(path: PathBuf, global_cache: GlobalHttpCacheRc) -> Self { - #[cfg(not(target_arch = "wasm32"))] - assert!(path.is_absolute()); - let manifest = LocalCacheManifest::new_for_lsp( - path.join("manifest.json"), - global_cache.sys.clone(), - ); - Self { - cache: LocalHttpCache { - path, - manifest, - global_cache, - // In the LSP, we disallow the cache from automatically copying from - // the global cache to the local cache for technical reasons. - // - // 1. We need to verify the checksums from the lockfile are correct when - // moving from the global to the local cache. - // 2. We need to verify the checksums for JSR https specifiers match what - // is found in the package's manifest. - allow_global_to_local: GlobalToLocalCopy::Disallow, - jsr_registry_url: None, // only used when GlobalToLocalCopy::Allow - }, - } - } - - // Url::from_file_path is not available in wasm, so add this cfg - #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] - pub fn get_file_url(&self, url: &Url) -> Option { - let sub_path = { - let data = self.cache.manifest.data.read(); - let maybe_content_type = - data.get(url).and_then(|d| d.content_type_header()); - url_to_local_sub_path(url, maybe_content_type).ok()? - }; - let path = sub_path.as_path_from_root(&self.cache.path); - if self.cache.env().fs_is_file_no_err(&path) { - Url::from_file_path(path).ok() - } else { - None - } - } - - pub fn get_remote_url(&self, path: &Path) -> Option { - let Ok(path) = path.strip_prefix(&self.cache.path) else { - return None; // not in this directory - }; - let components = path - .components() - .map(|c| c.as_os_str().to_string_lossy()) - .collect::>(); - if components - .last() - .map(|c| c.starts_with('#')) - .unwrap_or(false) - { - // the file itself will have an entry in the manifest - let data = self.cache.manifest.data.read(); - data.get_reverse_mapping(path) - } else if let Some(last_index) = - components.iter().rposition(|c| c.starts_with('#')) - { - // get the mapping to the deepest hashed directory and - // then add the remaining path components to the url - let dir_path: PathBuf = components[..last_index + 1].iter().fold( - PathBuf::new(), - |mut path, c| { - path.push(c.as_ref()); - path - }, - ); - let dir_url = self - .cache - .manifest - .data - .read() - .get_reverse_mapping(&dir_path)?; - let file_url = - dir_url.join(&components[last_index + 1..].join("/")).ok()?; - Some(file_url) - } else { - // we can work backwards from the path to the url - let mut parts = Vec::new(); - for (i, part) in path.components().enumerate() { - let part = part.as_os_str().to_string_lossy(); - if i == 0 { - let mut result = String::new(); - let part = if let Some(part) = part.strip_prefix("http_") { - result.push_str("http://"); - part - } else { - result.push_str("https://"); - &part - }; - if let Some((domain, port)) = part.rsplit_once('_') { - result.push_str(&format!("{}:{}", domain, port)); - } else { - result.push_str(part); - } - parts.push(result); - } else { - parts.push(part.to_string()); - } - } - Url::parse(&parts.join("/")).ok() - } - } -} - -impl HttpCache for LocalLspHttpCache { - fn cache_item_key<'a>( - &self, - url: &'a Url, - ) -> std::io::Result> { - self.cache.cache_item_key(url) - } - - fn contains(&self, url: &Url) -> bool { - self.cache.contains(url) - } - - fn set( - &self, - url: &Url, - headers: HeadersMap, - content: &[u8], - ) -> std::io::Result<()> { - self.cache.set(url, headers, content) - } - - fn get( - &self, - key: &HttpCacheItemKey, - maybe_checksum: Option, - ) -> Result, CacheReadFileError> { - self.cache.get(key, maybe_checksum) - } - - fn read_modified_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - self.cache.read_modified_time(key) - } - - fn read_headers( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - self.cache.read_headers(key) - } - - fn read_download_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - self.cache.read_modified_time(key) - } -} - -#[allow(clippy::disallowed_types)] -pub type LocalHttpCacheRc = crate::sync::MaybeArc>; - -#[derive(Debug)] -pub struct LocalHttpCache { - path: PathBuf, - manifest: LocalCacheManifest, - global_cache: GlobalHttpCacheRc, - allow_global_to_local: GlobalToLocalCopy, - jsr_registry_url: Option, -} - -impl LocalHttpCache { - pub fn new( - path: PathBuf, - global_cache: GlobalHttpCacheRc, - allow_global_to_local: GlobalToLocalCopy, - jsr_registry_url: Url, - ) -> Self { - #[cfg(not(target_arch = "wasm32"))] - assert!(path.is_absolute()); - let manifest = LocalCacheManifest::new( - path.join("manifest.json"), - global_cache.sys.clone(), - ); - Self { - path, - manifest, - global_cache, - allow_global_to_local, - jsr_registry_url: Some(jsr_registry_url), - } - } - - #[inline] - fn env(&self) -> &TSys { - &self.global_cache.sys - } - - fn get_url_headers(&self, url: &Url) -> std::io::Result> { - if let Some(metadata) = self.manifest.get_stored_headers(url) { - return Ok(Some(metadata)); - } - - // if the local path exists, don't copy the headers from the global cache - // to the local - let local_path = url_to_local_sub_path(url, None)?; - if self - .env() - .fs_is_file_no_err(local_path.as_path_from_root(&self.path)) - { - return Ok(Some(Default::default())); - } - - if !self.allow_global_to_local.is_true() { - return Ok(None); - } - - // not found locally, so try to copy from the global manifest - let global_key = self.global_cache.cache_item_key(url)?; - let Some(headers) = self.global_cache.read_headers(&global_key)? else { - return Ok(None); - }; - - let local_path = - url_to_local_sub_path(url, headers_content_type(&headers))?; - self.manifest.insert_data(local_path, url.clone(), headers); - - Ok(Some(self.manifest.get_stored_headers(url).unwrap_or_else( - || { - // if it's not in the stored headers at this point then that means - // the file has no headers that need to be stored for the local cache - Default::default() - }, - ))) - } - - pub fn local_path_for_url( - &self, - url: &Url, - ) -> std::io::Result> { - if let Some(headers) = self.get_url_headers(url)? { - let is_redirect = headers.contains_key("location"); - if is_redirect { - return Ok(None); - } - - let local_path = - url_to_local_sub_path(url, headers_content_type(&headers))?; - Ok(Some(local_path.as_path_from_root(&self.path))) - } else { - Ok(None) - } - } - - fn transform_content_on_copy_to_local<'a>( - &self, - url: &Url, - content: Cow<'a, [u8]>, - ) -> Cow<'a, [u8]> { - let Some(jsr_url) = &self.jsr_registry_url else { - return content; - }; - if is_jsr_version_metadata_url(url, jsr_url) - && let Some(data) = transform_jsr_version_metadata(&content) - { - return Cow::Owned(data); - } - content - } -} - -fn is_jsr_version_metadata_url(url: &Url, jsr_url: &Url) -> bool { - // example url: https://jsr.io/@david/dax/0.43.0_meta.json - let Some(suffix) = url.as_str().strip_prefix(jsr_url.as_str()) else { - return false; - }; - let Some(suffix) = suffix.strip_prefix('@') else { - return false; - }; - let Some(prefix) = suffix.strip_suffix("_meta.json") else { - return false; - }; - prefix.chars().filter(|c| *c == '/').count() == 2 -} - -fn transform_jsr_version_metadata(content: &[u8]) -> Option> { - let checksum = checksum(content); - let mut json_data = - serde_json::from_slice::(content).ok()?; - let obj = json_data.as_object_mut()?; - let keys_to_remove = obj - .keys() - .filter(|k| k.starts_with("moduleGraph")) - .cloned() - .collect::>(); - for key in keys_to_remove { - obj.remove(&key); - } - obj.insert("lockfileChecksum".into(), checksum.into()); - serde_json::to_vec(&json_data).ok() -} - -impl HttpCache for LocalHttpCache { - fn cache_item_key<'a>( - &self, - url: &'a Url, - ) -> std::io::Result> { - Ok(HttpCacheItemKey { - #[cfg(debug_assertions)] - is_local_key: true, - url, - file_path: None, // need to compute this every time - }) - } - - fn contains(&self, url: &Url) -> bool { - self - .get_url_headers(url) - .ok() - .map(|d| d.is_some()) - .unwrap_or(false) - } - - fn read_modified_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - #[cfg(debug_assertions)] - debug_assert!(key.is_local_key); - - if let Some(headers) = self.get_url_headers(key.url)? { - let local_path = - url_to_local_sub_path(key.url, headers_content_type(&headers))?; - if let Ok(metadata) = self - .env() - .fs_metadata(local_path.as_path_from_root(&self.path)) - && let Ok(modified_time) = metadata.modified() - { - return Ok(Some(modified_time)); - } - } - - // fallback to the global cache - let global_key = self.global_cache.cache_item_key(key.url)?; - self.global_cache.read_modified_time(&global_key) - } - - fn set( - &self, - url: &Url, - headers: HeadersMap, - content: &[u8], - ) -> std::io::Result<()> { - let is_redirect = headers.contains_key("location"); - let sub_path = url_to_local_sub_path(url, headers_content_type(&headers))?; - - if !is_redirect { - let content = - self.transform_content_on_copy_to_local(url, Cow::Borrowed(content)); - // Cache content - atomic_write_file_with_retries( - self.env(), - &sub_path.as_path_from_root(&self.path), - &content, - CACHE_PERM, - )?; - } - - self.manifest.insert_data(sub_path, url.clone(), headers); - - Ok(()) - } - - fn get( - &self, - key: &HttpCacheItemKey, - maybe_checksum: Option, - ) -> Result, CacheReadFileError> { - #[cfg(debug_assertions)] - debug_assert!(key.is_local_key); - - let maybe_headers = self.get_url_headers(key.url)?; - match maybe_headers { - Some(headers) => { - let is_redirect = headers.contains_key("location"); - let bytes: Cow<'static, [u8]> = if is_redirect { - // return back an empty file for redirect - Cow::Borrowed(&[]) - } else { - // if it's not a redirect, then it should have a file path - let local_file_path = - url_to_local_sub_path(key.url, headers_content_type(&headers))? - .as_path_from_root(&self.path); - let file_bytes_result = self.env().fs_read(&local_file_path); - match file_bytes_result { - Ok(bytes) => bytes, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - if self.allow_global_to_local.is_true() { - // only check the checksum when copying from the global to the local cache - let global_key = self.global_cache.cache_item_key(key.url)?; - let maybe_global_cache_file = - self.global_cache.get(&global_key, maybe_checksum)?; - if let Some(file) = maybe_global_cache_file { - let content = self - .transform_content_on_copy_to_local(key.url, file.content); - atomic_write_file_with_retries( - self.env(), - &local_file_path, - &content, - CACHE_PERM, - )?; - content - } else { - return Ok(None); - } - } else { - return Ok(None); - } - } - Err(err) => return Err(CacheReadFileError::Io(err)), - } - }; - Ok(Some(CacheEntry { - metadata: SerializedCachedUrlMetadata { - headers, - url: key.url.to_string(), - // not used for the local cache - time: None, - }, - content: bytes, - })) - } - None => Ok(None), - } - } - - fn read_headers( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - #[cfg(debug_assertions)] - debug_assert!(key.is_local_key); - - self.get_url_headers(key.url) - } - - fn read_download_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - // This will never be called for the local cache in practice - // because only the LSP ever reads this time for telling if - // a file should be re-downloaded when respecting cache headers - // and it only does this using a global cache - self.read_modified_time(key) - } -} - -pub(super) struct LocalCacheSubPath<'a> { - pub has_hash: bool, - pub parts: Vec>, -} - -impl LocalCacheSubPath<'_> { - pub fn as_path_from_root(&self, root_path: &Path) -> PathBuf { - let mut path = root_path.to_path_buf(); - for part in &self.parts { - path.push(part.as_ref()); - } - path - } - - pub fn as_relative_path(&self) -> PathBuf { - let mut path = - PathBuf::with_capacity(self.parts.iter().map(|p| p.len() + 1).sum()); - for part in &self.parts { - path.push(part.as_ref()); - } - path - } -} - -fn headers_content_type(headers: &HeadersMap) -> Option<&str> { - headers.get("content-type").map(|s| s.as_str()) -} - -fn url_to_local_sub_path<'a>( - url: &'a Url, - content_type: Option<&str>, -) -> std::io::Result> { - // https://stackoverflow.com/a/31976060/188246 - static FORBIDDEN_CHARS: Lazy> = Lazy::new(|| { - HashSet::from(['?', '<', '>', ':', '*', '|', '\\', ':', '"', '\'', '/']) - }); - // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file - static FORBIDDEN_WINDOWS_NAMES: Lazy> = - Lazy::new(|| { - let set = HashSet::from([ - "con", "prn", "aux", "nul", "com0", "com1", "com2", "com3", "com4", - "com5", "com6", "com7", "com8", "com9", "lpt0", "lpt1", "lpt2", "lpt3", - "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", - ]); - // ensure everything is lowercase because we'll be comparing - // lowercase filenames against this - debug_assert!(set.iter().all(|s| s.to_lowercase() == *s)); - set - }); - - fn has_forbidden_chars(segment: &str) -> bool { - segment.chars().any(|c| { - let is_uppercase = c.is_ascii_alphabetic() && !c.is_ascii_lowercase(); - FORBIDDEN_CHARS.contains(&c) - // do not allow uppercase letters in order to make this work - // the same on case insensitive file systems - || is_uppercase - }) - } - - fn has_known_extension(path: &str) -> bool { - let path = path.to_lowercase(); - path.ends_with(".js") - || path.ends_with(".ts") - || path.ends_with(".jsx") - || path.ends_with(".tsx") - || path.ends_with(".mts") - || path.ends_with(".mjs") - || path.ends_with(".json") - || path.ends_with(".wasm") - } - - fn get_extension(url: &Url, content_type: Option<&str>) -> &'static str { - let media_type = - MediaType::from_specifier_and_content_type(url, content_type); - match media_type { - MediaType::JavaScript => ".js", - MediaType::Jsx => ".jsx", - MediaType::Mjs => ".mjs", - MediaType::Cjs => ".cjs", - MediaType::TypeScript => ".ts", - MediaType::Mts => ".mts", - MediaType::Cts => ".cts", - MediaType::Dts => ".d.ts", - MediaType::Dmts => ".d.mts", - MediaType::Dcts => ".d.cts", - MediaType::Tsx => ".tsx", - MediaType::Css => ".css", - MediaType::Json => ".json", - MediaType::Jsonc => ".jsonc", - MediaType::Json5 => ".json5", - MediaType::Markdown => ".md", - MediaType::Wasm => ".wasm", - MediaType::SourceMap => ".js", - MediaType::Html => ".html", - MediaType::Sql => ".sql", - MediaType::Unknown => ".js", - } - } - - fn short_hash(data: &str, last_ext: Option<&str>) -> String { - // This function is a bit of a balancing act between readability - // and avoiding collisions. - let hash = checksum(data.as_bytes()); - // keep the paths short because of windows path limit - const MAX_LENGTH: usize = 20; - let mut sub = String::with_capacity(MAX_LENGTH); - for c in data.chars().take(MAX_LENGTH) { - // don't include the query string (only use it in the hash) - if c == '?' { - break; - } - if FORBIDDEN_CHARS.contains(&c) { - sub.push('_'); - } else { - sub.extend(c.to_lowercase()); - } - } - let sub = match last_ext { - Some(ext) => sub.strip_suffix(ext).unwrap_or(&sub), - None => &sub, - }; - let ext = last_ext.unwrap_or(""); - if sub.is_empty() { - format!("#{}{}", &hash[..7], ext) - } else { - format!("#{}_{}{}", &sub, &hash[..5], ext) - } - } - - fn should_hash_part(part: &str, last_ext: Option<&str>) -> bool { - if part.is_empty() || part.len() > 30 { - // keep short due to windows path limit - return true; - } - let hash_context_specific = if let Some(last_ext) = last_ext { - // if the last part does not have a known extension, hash it in order to - // prevent collisions with a directory of the same name - !has_known_extension(part) || !part.ends_with(last_ext) - } else { - // if any non-ending path part has a known extension, hash it in order to - // prevent collisions where a filename has the same name as a directory name - has_known_extension(part) - }; - - // the hash symbol at the start designates a hash for the url part - hash_context_specific - || part.starts_with('#') - || has_forbidden_chars(part) - || last_ext.is_none() && FORBIDDEN_WINDOWS_NAMES.contains(part) - || part.ends_with('.') - } - - // get the base url - let port_separator = "_"; // make this shorter with just an underscore - let Some(mut base_parts) = base_url_to_filename_parts(url, port_separator) - else { - return Err(std::io::Error::new( - ErrorKind::InvalidInput, - format!("Can't convert url (\"{}\") to filename.", url), - )); - }; - - if base_parts[0] == "https" { - base_parts.remove(0); - } else { - let scheme = base_parts.remove(0); - base_parts[0] = Cow::Owned(format!("{}_{}", scheme, base_parts[0])); - } - - // first, try to get the filename of the path - let path_segments = url_path_segments(url); - let mut parts = base_parts - .into_iter() - .chain(path_segments.map(Cow::Borrowed)) - .collect::>(); - - // push the query parameter onto the last part - if let Some(query) = url.query() { - let last_part = parts.last_mut().unwrap(); - let last_part = match last_part { - Cow::Borrowed(_) => { - *last_part = Cow::Owned(last_part.to_string()); - match last_part { - Cow::Borrowed(_) => unreachable!(), - Cow::Owned(s) => s, - } - } - Cow::Owned(last_part) => last_part, - }; - last_part.push('?'); - last_part.push_str(query); - } - - let mut has_hash = false; - let parts_len = parts.len(); - let parts = parts - .into_iter() - .enumerate() - .map(|(i, part)| { - let is_last = i == parts_len - 1; - let last_ext = if is_last { - Some(get_extension(url, content_type)) - } else { - None - }; - if should_hash_part(&part, last_ext) { - has_hash = true; - Cow::Owned(short_hash(&part, last_ext)) - } else { - part - } - }) - .collect::>(); - - Ok(LocalCacheSubPath { has_hash, parts }) -} - -#[derive(Debug)] -struct LocalCacheManifest< - Sys: FsCreateDirAll - + FsMetadata - + FsOpen - + FsRead - + FsRemoveFile - + FsRename - + ThreadSleep - + SystemRandom - + MaybeSend - + MaybeSync - + std::fmt::Debug, -> { - sys: Sys, - file_path: PathBuf, - data: RwLock, -} - -impl< - Sys: FsCreateDirAll - + FsMetadata - + FsOpen - + FsRead - + FsRemoveFile - + FsRename - + ThreadSleep - + SystemRandom - + MaybeSend - + MaybeSync - + std::fmt::Debug - + Clone, -> LocalCacheManifest -{ - pub fn new(file_path: PathBuf, sys: Sys) -> Self { - Self::new_internal(file_path, false, sys) - } - - pub fn new_for_lsp(file_path: PathBuf, sys: Sys) -> Self { - Self::new_internal(file_path, true, sys) - } - - fn new_internal( - file_path: PathBuf, - use_reverse_mapping: bool, - sys: Sys, - ) -> Self { - let text = sys - .fs_read(&file_path) - .ok() - .and_then(|bytes| String::from_utf8(bytes.into_owned()).ok()); - Self { - sys, - data: RwLock::new(manifest::LocalCacheManifestData::new( - text.as_deref(), - use_reverse_mapping, - )), - file_path, - } - } - - pub fn insert_data( - &self, - sub_path: LocalCacheSubPath, - url: Url, - mut original_headers: HashMap, - ) { - fn should_keep_content_type_header( - url: &Url, - headers: &HashMap, - ) -> bool { - // only keep the location header if it can't be derived from the url - MediaType::from_specifier(url) - != MediaType::from_specifier_and_headers(url, Some(headers)) - } - - let mut headers_subset = BTreeMap::new(); - - const HEADER_KEYS_TO_KEEP: [&str; 4] = [ - // keep alphabetical for cleanliness in the output - "content-type", - "location", - "x-deno-warning", - "x-typescript-types", - ]; - for key in HEADER_KEYS_TO_KEEP { - if key == "content-type" - && !should_keep_content_type_header(&url, &original_headers) - { - continue; - } - if let Some((k, v)) = original_headers.remove_entry(key) { - headers_subset.insert(k, v); - } - } - - let mut data = self.data.write(); - let add_module_entry = headers_subset.is_empty() - && !sub_path - .parts - .last() - .map(|s| s.starts_with('#')) - .unwrap_or(false); - let mut has_changed = if add_module_entry { - data.remove(&url, &sub_path) - } else { - let new_data = manifest::SerializedLocalCacheManifestDataModule { - headers: headers_subset, - }; - if data.get(&url) == Some(&new_data) { - false - } else { - data.insert(url.clone(), &sub_path, new_data); - true - } - }; - - if sub_path.has_hash { - let url_path_parts = url_path_segments(&url).collect::>(); - let base_url = { - let mut url = url.clone(); - url.set_path("/"); - url.set_query(None); - url.set_fragment(None); - url - }; - for (i, local_part) in sub_path.parts[1..sub_path.parts.len() - 1] - .iter() - .enumerate() - { - if local_part.starts_with('#') { - let mut url = base_url.clone(); - url.set_path(&format!("{}/", url_path_parts[..i + 1].join("/"))); - if data.add_directory(url, sub_path.parts[..i + 2].join("/")) { - has_changed = true; - } - } - } - } - - if has_changed { - // don't bother ensuring the directory here because it will - // eventually be created by files being added to the cache - let result = atomic_write_file_with_retries( - &self.sys, - &self.file_path, - data.as_json().as_bytes(), - CACHE_PERM, - ); - if let Err(err) = result { - log::debug!("Failed saving local cache manifest: {:#}", err); - } - } - } - - pub fn get_stored_headers(&self, url: &Url) -> Option { - let data = self.data.read(); - data.get(url).map(|module| { - module - .headers - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect::>() - }) - } -} - -// This is in a separate module in order to enforce keeping -// the internal implementation private. -mod manifest { - use std::collections::BTreeMap; - use std::path::Path; - use std::path::PathBuf; - - use serde::Deserialize; - use serde::Serialize; - use url::Url; - - use super::LocalCacheSubPath; - use super::url_to_local_sub_path; - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - pub struct SerializedLocalCacheManifestDataModule { - #[serde( - default = "BTreeMap::new", - skip_serializing_if = "BTreeMap::is_empty" - )] - pub headers: BTreeMap, - } - - impl SerializedLocalCacheManifestDataModule { - pub fn content_type_header(&self) -> Option<&str> { - self.headers.get("content-type").map(|s| s.as_str()) - } - } - - // Using BTreeMap to make sure that the data is always sorted - #[derive(Debug, Default, Clone, Serialize, Deserialize)] - struct SerializedLocalCacheManifestData { - #[serde( - default = "BTreeMap::new", - skip_serializing_if = "BTreeMap::is_empty" - )] - pub folders: BTreeMap, - #[serde( - default = "BTreeMap::new", - skip_serializing_if = "BTreeMap::is_empty" - )] - pub modules: BTreeMap, - } - - #[derive(Debug, Default, Clone)] - pub(super) struct LocalCacheManifestData { - serialized: SerializedLocalCacheManifestData, - // reverse mapping used in the lsp - reverse_mapping: Option>, - } - - impl LocalCacheManifestData { - pub fn new(maybe_text: Option<&str>, use_reverse_mapping: bool) -> Self { - let serialized: SerializedLocalCacheManifestData = maybe_text - .and_then(|text| match serde_json::from_str(text) { - Ok(data) => Some(data), - Err(err) => { - log::debug!("Failed deserializing local cache manifest: {:#}", err); - None - } - }) - .unwrap_or_default(); - let reverse_mapping = if use_reverse_mapping { - Some( - serialized - .modules - .iter() - .filter_map(|(url, module)| { - if module.headers.contains_key("location") { - return None; - } - url_to_local_sub_path(url, module.content_type_header()) - .ok() - .map(|local_path| { - let path = if cfg!(windows) { - PathBuf::from(local_path.parts.join("\\")) - } else { - PathBuf::from(local_path.parts.join("/")) - }; - (path, url.clone()) - }) - }) - .chain(serialized.folders.iter().map(|(url, local_path)| { - let path = if cfg!(windows) { - PathBuf::from(local_path.replace('/', "\\")) - } else { - PathBuf::from(local_path) - }; - (path, url.clone()) - })) - .collect::>(), - ) - } else { - None - }; - Self { - serialized, - reverse_mapping, - } - } - - pub fn get( - &self, - url: &Url, - ) -> Option<&SerializedLocalCacheManifestDataModule> { - self.serialized.modules.get(url) - } - - pub fn get_reverse_mapping(&self, path: &Path) -> Option { - debug_assert!(self.reverse_mapping.is_some()); // only call this if you're in the lsp - self - .reverse_mapping - .as_ref() - .and_then(|mapping| mapping.get(path)) - .cloned() - } - - pub fn add_directory(&mut self, url: Url, local_path: String) -> bool { - if let Some(current) = self.serialized.folders.get(&url) - && *current == local_path - { - return false; - } - - if let Some(reverse_mapping) = &mut self.reverse_mapping { - reverse_mapping.insert( - if cfg!(windows) { - PathBuf::from(local_path.replace('/', "\\")) - } else { - PathBuf::from(&local_path) - }, - url.clone(), - ); - } - - self.serialized.folders.insert(url, local_path); - true - } - - pub fn insert( - &mut self, - url: Url, - sub_path: &LocalCacheSubPath, - new_data: SerializedLocalCacheManifestDataModule, - ) { - if let Some(reverse_mapping) = &mut self.reverse_mapping { - reverse_mapping.insert(sub_path.as_relative_path(), url.clone()); - } - self.serialized.modules.insert(url, new_data); - } - - pub fn remove(&mut self, url: &Url, sub_path: &LocalCacheSubPath) -> bool { - if self.serialized.modules.remove(url).is_some() { - if let Some(reverse_mapping) = &mut self.reverse_mapping { - reverse_mapping.remove(&sub_path.as_relative_path()); - } - true - } else { - false - } - } - - pub fn as_json(&self) -> String { - serde_json::to_string_pretty(&self.serialized).unwrap() - } - } -} - -fn url_path_segments(url: &Url) -> impl Iterator { - url - .path() - .strip_prefix('/') - .unwrap_or(url.path()) - .split('/') -} - -#[cfg(test)] -mod test { - use super::*; - use crate::GlobalHttpCache; - use std::rc::Rc; - - use pretty_assertions::assert_eq; - use sys_traits::impls::RealSys; - use tempfile::{TempDir, tempdir}; - - struct TestCaches { - global_cache: GlobalHttpCacheRc, - local_cache: LocalHttpCacheRc, - local_temp: PathBuf, - _temp: TempDir, - } - - impl TestCaches { - fn new() -> Self { - let temp = tempdir().unwrap(); - let global_temp = temp.path().join("global"); - let local_temp = temp.path().join("local"); - - let global_cache = GlobalHttpCache::new(RealSys, global_temp); - let global_cache = Rc::new(global_cache); - let local_cache = Rc::new(LocalHttpCache::new( - local_temp.clone(), - global_cache.clone(), - GlobalToLocalCopy::Allow, - Url::parse("https://jsr.io/").unwrap(), - )); - Self { - global_cache, - local_cache, - local_temp, - _temp: temp, - } - } - } - - #[test] - fn test_url_to_local_sub_path() { - run_test("https://deno.land/x/mod.ts", &[], "deno.land/x/mod.ts"); - run_test( - "http://deno.land/x/mod.ts", - &[], - // http gets added to the folder name, but not https - "http_deno.land/x/mod.ts", - ); - run_test( - // capital letter in filename - "https://deno.land/x/MOD.ts", - &[], - "deno.land/x/#mod_fa860.ts", - ); - run_test( - // query string - "https://deno.land/x/mod.ts?testing=1", - &[], - "deno.land/x/#mod_2eb80.ts", - ); - run_test( - // capital letter in directory - "https://deno.land/OTHER/mod.ts", - &[], - "deno.land/#other_1c55d/mod.ts", - ); - run_test( - // under max of 30 chars - "https://deno.land/x/012345678901234567890123456.js", - &[], - "deno.land/x/012345678901234567890123456.js", - ); - run_test( - // max 30 chars - "https://deno.land/x/0123456789012345678901234567.js", - &[], - "deno.land/x/#01234567890123456789_836de.js", - ); - run_test( - // forbidden char - "https://deno.land/x/mod's.js", - &[], - "deno.land/x/#mod_s_44fc8.js", - ); - run_test( - // no extension - "https://deno.land/x/mod", - &[("content-type", "application/typescript")], - "deno.land/x/#mod_e55cf.ts", - ); - run_test( - // known extension in directory is not allowed - // because it could conflict with a file of the same name - "https://deno.land/x/mod.js/mod.js", - &[], - "deno.land/x/#mod.js_59c58/mod.js", - ); - run_test( - // slash slash in path - "http://localhost//mod.js", - &[], - "http_localhost/#e3b0c44/mod.js", - ); - run_test( - // headers same extension - "https://deno.land/x/mod.ts", - &[("content-type", "application/typescript")], - "deno.land/x/mod.ts", - ); - run_test( - // headers different extension... We hash this because - // if someone deletes the manifest file, then we don't want - // https://deno.land/x/mod.ts to resolve as a typescript file - "https://deno.land/x/mod.ts", - &[("content-type", "application/javascript")], - "deno.land/x/#mod.ts_e8c36.js", - ); - run_test( - // not allowed windows folder name - "https://deno.land/x/con/con.ts", - &[], - "deno.land/x/#con_1143d/con.ts", - ); - run_test( - // disallow ending a directory with a period - // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file - "https://deno.land/x/test./main.ts", - &[], - "deno.land/x/#test._4ee3d/main.ts", - ); - run_test("https://deno.land/x/mod.wasm", &[], "deno.land/x/mod.wasm"); - - #[track_caller] - fn run_test(url: &str, headers: &[(&str, &str)], expected: &str) { - let test_caches = TestCaches::new(); - - let url = Url::parse(url).unwrap(); - let headers: HashMap = headers - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(); - test_caches - .global_cache - .set(&url, headers.clone(), b"console.log('hello');") - .unwrap(); - let path = test_caches - .local_cache - .local_path_for_url(&url) - .unwrap() - .unwrap(); - let result = - url_to_local_sub_path(&url, headers_content_type(&headers)).unwrap(); - let parts = result.parts.join("/"); - assert_eq!(path, test_caches.local_temp.join(&parts)); - assert_eq!(parts, expected); - assert_eq!( - result.parts.iter().any(|p| p.starts_with('#')), - result.has_hash - ) - } - } - - #[test] - fn local_path_no_headers() { - let test_caches = TestCaches::new(); - let url = Url::parse("https://deno.land/x/mod.ts").unwrap(); - assert!(matches!( - test_caches.local_cache.local_path_for_url(&url), - Ok(None) - )); - } - - #[test] - fn local_path_redirect() { - let test_caches = TestCaches::new(); - let url = Url::parse("https://deno.land/x/mod.ts").unwrap(); - test_caches - .global_cache - .set( - &url, - HashMap::from([( - "location".to_string(), - "https://deno.land/x/mod.ts".to_string(), - )]), - b"", - ) - .unwrap(); - assert!(matches!( - test_caches.local_cache.local_path_for_url(&url), - Ok(None) - )); - } - - #[test] - fn test_copy_version_metadata_file() { - let test_caches = TestCaches::new(); - let data = - r#"{ "moduleGraph2": "testing", "checksums": { "test": "test" } }"#; - // has the moduleGraph2 property stripped - let expected_data = r#"{"checksums":{"test":"test"},"lockfileChecksum":"dc108ae9ffb13086cb1551692960c21893991a7f0f9dc770814ff21522fd1d48"}"#; - { - let metadata_url = - Url::parse("https://jsr.io/@david/dax/1.2.3_meta.json").unwrap(); - test_caches - .global_cache - .set(&metadata_url, Default::default(), data.as_bytes()) - .unwrap(); - let key = test_caches - .local_cache - .cache_item_key(&metadata_url) - .unwrap(); - let final_data = - test_caches.local_cache.get(&key, None).unwrap().unwrap(); - assert_eq!( - String::from_utf8(final_data.content.to_vec()).unwrap(), - expected_data, - ); - } - { - // now try just setting directly in the local cache - let metadata_url = - Url::parse("https://jsr.io/@david/dax/1.2.2_meta.json").unwrap(); - test_caches - .local_cache - .set(&metadata_url, Default::default(), data.as_bytes()) - .unwrap(); - let key = test_caches - .local_cache - .cache_item_key(&metadata_url) - .unwrap(); - let final_data = - test_caches.local_cache.get(&key, None).unwrap().unwrap(); - assert_eq!( - String::from_utf8(final_data.content.to_vec()).unwrap(), - expected_data, - ); - } - } - - #[test] - fn test_is_jsr_version_metadata_url() { - let cases = [ - ("https://jsr.io/@test/test/1.2.3_meta.json", true), - ("https://jsr.io/@test/test/test/1.2.3_meta.json", false), - ("https://jsr.io/@test/test/meta.json", false), - ("https://jsr.io/test/test/1.2.3_meta.json", false), - ("https://jsr.com/@test/test/1.2.3_meta.json", false), - ]; - let jsr_url = Url::parse("https://jsr.io/").unwrap(); - for (url, expected) in cases { - let value = - is_jsr_version_metadata_url(&Url::parse(url).unwrap(), &jsr_url); - assert_eq!(value, expected); - } - } - - #[test] - fn test_transform_jsr_version_metadata() { - let cases = [ - ( - r#"{ "moduleGraph1": "data", "moduleGraph2": "data", "moduleGraph3": "data", "other": "data" }"#, - Some( - r#"{"other":"data","lockfileChecksum":"1438025e1aa277249e97fa99a2283542ab1156b0967c3e0997f78bee22d121ad"}"#, - ), - ), - ( - r#"{ "other": "data" }"#, - Some( - r#"{"other":"data","lockfileChecksum":"62db4d2ded7cb6348ccb1648b2a27ed96dbe0fadc42c8359024c0213bab2f0e5"}"#, - ), - ), - ]; - - for (input, expected) in cases { - let output = transform_jsr_version_metadata(input.as_bytes()); - assert_eq!( - output.map(|o| String::from_utf8(o).unwrap()).as_deref(), - expected - ) - } - } -} diff --git a/rs_lib/src/memory.rs b/rs_lib/src/memory.rs deleted file mode 100644 index 1cef0de..0000000 --- a/rs_lib/src/memory.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::borrow::Cow; -use std::collections::HashMap; -use std::time::UNIX_EPOCH; - -use parking_lot::Mutex; -use sys_traits::SystemTimeNow; -use url::Url; - -use crate::CacheEntry; -use crate::CacheReadFileError; -use crate::Checksum; -use crate::HeadersMap; -use crate::HttpCache; -use crate::HttpCacheItemKey; -use crate::SerializedCachedUrlMetadata; -use crate::sync::MaybeSend; -use crate::sync::MaybeSync; - -#[cfg(not(target_arch = "wasm32"))] -#[derive(Debug)] -pub struct MemoryHttpCacheSystemTimeClock; - -#[cfg(not(target_arch = "wasm32"))] -impl sys_traits::SystemTimeNow for MemoryHttpCacheSystemTimeClock { - fn sys_time_now(&self) -> std::time::SystemTime { - #[allow(clippy::disallowed_methods)] - std::time::SystemTime::now() - } -} - -/// A simple in-memory cache mostly useful for testing. -#[derive(Debug)] -pub struct MemoryHttpCache { - cache: Mutex>, - clock: TSys, -} - -#[cfg(not(target_arch = "wasm32"))] -impl Default for MemoryHttpCache { - fn default() -> Self { - Self::new(MemoryHttpCacheSystemTimeClock) - } -} - -impl - MemoryHttpCache -{ - pub fn new(clock: TSys) -> Self { - Self { - cache: Mutex::new(HashMap::new()), - clock, - } - } -} - -impl HttpCache - for MemoryHttpCache -{ - fn cache_item_key<'a>( - &self, - url: &'a Url, - ) -> std::io::Result> { - Ok(HttpCacheItemKey { - #[cfg(debug_assertions)] - is_local_key: false, - url, - file_path: None, - }) - } - - fn contains(&self, url: &Url) -> bool { - self.cache.lock().contains_key(url) - } - - fn set( - &self, - url: &Url, - headers: HeadersMap, - content: &[u8], - ) -> std::io::Result<()> { - self.cache.lock().insert( - url.clone(), - CacheEntry { - metadata: SerializedCachedUrlMetadata { - headers, - url: url.to_string(), - time: Some( - self - .clock - .sys_time_now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - ), - }, - content: Cow::Owned(content.to_vec()), - }, - ); - Ok(()) - } - - fn get( - &self, - key: &HttpCacheItemKey, - maybe_checksum: Option, - ) -> Result, CacheReadFileError> { - self - .cache - .lock() - .get(key.url) - .cloned() - .map(|entry| { - if let Some(checksum) = maybe_checksum { - checksum - .check(key.url, &entry.content) - .map_err(CacheReadFileError::ChecksumIntegrity)?; - } - Ok(entry) - }) - .transpose() - } - - fn read_modified_time( - &self, - _key: &HttpCacheItemKey, - ) -> std::io::Result> { - Ok(None) // for now - } - - fn read_headers( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - Ok( - self - .cache - .lock() - .get(key.url) - .map(|entry| entry.metadata.headers.clone()), - ) - } - - fn read_download_time( - &self, - key: &HttpCacheItemKey, - ) -> std::io::Result> { - Ok(self.cache.lock().get(key.url).and_then(|entry| { - entry - .metadata - .time - .map(|time| UNIX_EPOCH + std::time::Duration::from_secs(time)) - })) - } -} diff --git a/rs_lib/src/npm.rs b/rs_lib/src/npm.rs deleted file mode 100644 index 4e41802..0000000 --- a/rs_lib/src/npm.rs +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -use std::borrow::Cow; -use std::io::ErrorKind; -use std::path::Path; -use std::path::PathBuf; - -use deno_path_util::normalize_path; -use deno_path_util::url_from_directory_path; -use sys_traits::FsCanonicalize; -use sys_traits::FsCreateDirAll; -use url::Url; - -pub struct NpmCacheFolderId { - /// Package name. - pub name: String, - /// Package version. - pub version: String, - /// Package copy index. - pub copy_index: u8, -} - -/// The global cache directory of npm packages. -#[derive(Clone, Debug)] -pub struct NpmCacheDir { - root_dir: PathBuf, - // cached url representation of the root directory - root_dir_url: Url, - // A list of all registry that were discovered via `.npmrc` files - // turned into a safe directory names. - known_registries_dirnames: Vec, -} - -impl NpmCacheDir { - pub fn new( - sys: &Sys, - root_dir: PathBuf, - known_registries_urls: Vec, - ) -> Self { - fn try_get_canonicalized_root_dir( - sys: &Sys, - root_dir: &Path, - ) -> Result { - match sys.fs_canonicalize(root_dir) { - Ok(path) => Ok(path), - Err(err) if err.kind() == ErrorKind::NotFound => { - sys.fs_create_dir_all(root_dir)?; - sys.fs_canonicalize(root_dir) - } - Err(err) => Err(err), - } - } - - // this may fail on readonly file systems, so just ignore if so - let root_dir = normalize_path(Cow::Owned(root_dir)); - let root_dir = try_get_canonicalized_root_dir(sys, &root_dir) - .map(Cow::Owned) - .unwrap_or(root_dir); - let root_dir_url = url_from_directory_path(&root_dir).unwrap(); - - let known_registries_dirnames: Vec<_> = known_registries_urls - .into_iter() - .map(|url| { - root_url_to_safe_local_dirname(&url) - .to_string_lossy() - .replace('\\', "/") - }) - .collect(); - - Self { - root_dir: root_dir.into_owned(), - root_dir_url, - known_registries_dirnames, - } - } - - pub fn root_dir(&self) -> &Path { - &self.root_dir - } - - pub fn root_dir_url(&self) -> &Url { - &self.root_dir_url - } - - pub fn package_folder_for_id( - &self, - package_name: &str, - package_version: &str, - package_copy_index: u8, - registry_url: &Url, - ) -> PathBuf { - if package_copy_index == 0 { - self - .package_name_folder(package_name, registry_url) - .join(package_version) - } else { - self - .package_name_folder(package_name, registry_url) - .join(format!("{}_{}", package_version, package_copy_index)) - } - } - - pub fn package_name_folder(&self, name: &str, registry_url: &Url) -> PathBuf { - let mut dir = self.registry_folder(registry_url); - if name.to_lowercase() != name { - let encoded_name = mixed_case_package_name_encode(name); - // Using the encoded directory may have a collision with an actual package name - // so prefix it with an underscore since npm packages can't start with that - dir.join(format!("_{encoded_name}")) - } else { - // ensure backslashes are used on windows - for part in name.split('/') { - dir = dir.join(part); - } - dir - } - } - - fn registry_folder(&self, registry_url: &Url) -> PathBuf { - self - .root_dir - .join(root_url_to_safe_local_dirname(registry_url)) - } - - pub fn resolve_package_folder_id_from_specifier( - &self, - specifier: &Url, - ) -> Option { - let mut maybe_relative_url = None; - - // Iterate through known registries and try to get a match. - for registry_dirname in &self.known_registries_dirnames { - let registry_root_dir = self - .root_dir_url - .join(&format!("{}/", registry_dirname)) - // this not succeeding indicates a fatal issue, so unwrap - .unwrap(); - - let Some(relative_url) = registry_root_dir.make_relative(specifier) - else { - continue; - }; - - if relative_url.starts_with("../") { - continue; - } - - maybe_relative_url = Some(relative_url); - break; - } - - let mut relative_url = maybe_relative_url?; - - // base32 decode the url if it starts with an underscore - // * Ex. _{base32(package_name)}/ - if let Some(end_url) = relative_url.strip_prefix('_') { - let mut parts = end_url - .split('/') - .map(ToOwned::to_owned) - .collect::>(); - match mixed_case_package_name_decode(&parts[0]) { - Some(part) => { - parts[0] = part; - } - None => return None, - } - relative_url = parts.join("/"); - } - - // examples: - // * chalk/5.0.1/ - // * @types/chalk/5.0.1/ - // * some-package/5.0.1_1/ -- where the `_1` (/_\d+/) is a copy of the folder for peer deps - let is_scoped_package = relative_url.starts_with('@'); - let mut parts = relative_url - .split('/') - .enumerate() - .take(if is_scoped_package { 3 } else { 2 }) - .map(|(_, part)| part) - .collect::>(); - if parts.len() < 2 { - return None; - } - let version_part = parts.pop().unwrap(); - let name = parts.join("/"); - let (version, copy_index) = - if let Some((version, copy_count)) = version_part.split_once('_') { - (version, copy_count.parse::().ok()?) - } else { - (version_part, 0) - }; - Some(NpmCacheFolderId { - name, - version: version.to_string(), - copy_index, - }) - } - - pub fn get_cache_location(&self) -> PathBuf { - self.root_dir.clone() - } -} - -pub fn mixed_case_package_name_encode(name: &str) -> String { - // use base32 encoding because it's reversible and the character set - // only includes the characters within 0-9 and A-Z so it can be lower cased - base32::encode( - base32::Alphabet::Rfc4648Lower { padding: false }, - name.as_bytes(), - ) - .to_lowercase() -} - -pub fn mixed_case_package_name_decode(name: &str) -> Option { - base32::decode(base32::Alphabet::Rfc4648Lower { padding: false }, name) - .and_then(|b| String::from_utf8(b).ok()) -} - -/// Gets a safe local directory name for the provided url. -/// -/// For example: -/// https://deno.land:8080/path -> deno.land_8080/path -fn root_url_to_safe_local_dirname(root: &Url) -> PathBuf { - fn sanitize_segment(text: &str) -> String { - text - .chars() - .map(|c| if is_banned_segment_char(c) { '_' } else { c }) - .collect() - } - - fn is_banned_segment_char(c: char) -> bool { - matches!(c, '/' | '\\') || is_banned_path_char(c) - } - - let mut result = String::new(); - if let Some(domain) = root.domain() { - result.push_str(&sanitize_segment(domain)); - } - if let Some(port) = root.port() { - if !result.is_empty() { - result.push('_'); - } - result.push_str(&port.to_string()); - } - let mut result = PathBuf::from(result); - if let Some(segments) = root.path_segments() { - for segment in segments.filter(|s| !s.is_empty()) { - result = result.join(sanitize_segment(segment)); - } - } - - result -} - -/// Gets if the provided character is not supported on all -/// kinds of file systems. -fn is_banned_path_char(c: char) -> bool { - matches!(c, '<' | '>' | ':' | '"' | '|' | '?' | '*') -} - -#[cfg(test)] -mod test { - use std::path::PathBuf; - - use sys_traits::FsCreateDirAll; - use url::Url; - - use super::NpmCacheDir; - - #[test] - fn should_get_package_folder() { - let sys = sys_traits::impls::InMemorySys::default(); - let root_dir = if cfg!(windows) { - PathBuf::from("C:\\cache") - } else { - PathBuf::from("/cache") - }; - sys.fs_create_dir_all(&root_dir).unwrap(); - let registry_url = Url::parse("https://registry.npmjs.org/").unwrap(); - let cache = - NpmCacheDir::new(&sys, root_dir.clone(), vec![registry_url.clone()]); - - assert_eq!( - cache.package_folder_for_id("json", "1.2.5", 0, ®istry_url,), - root_dir - .join("registry.npmjs.org") - .join("json") - .join("1.2.5"), - ); - - assert_eq!( - cache.package_folder_for_id("json", "1.2.5", 1, ®istry_url,), - root_dir - .join("registry.npmjs.org") - .join("json") - .join("1.2.5_1"), - ); - - assert_eq!( - cache.package_folder_for_id("JSON", "2.1.5", 0, ®istry_url,), - root_dir - .join("registry.npmjs.org") - .join("_jjju6tq") - .join("2.1.5"), - ); - - assert_eq!( - cache.package_folder_for_id("@types/JSON", "2.1.5", 0, ®istry_url,), - root_dir - .join("registry.npmjs.org") - .join("_ib2hs4dfomxuuu2pjy") - .join("2.1.5"), - ); - } -} diff --git a/rs_lib/src/sync.rs b/rs_lib/src/sync.rs deleted file mode 100644 index 18d4c96..0000000 --- a/rs_lib/src/sync.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -pub use inner::*; - -#[cfg(feature = "sync")] -mod inner { - #![allow(clippy::disallowed_types)] - - pub use core::marker::Send as MaybeSend; - pub use core::marker::Sync as MaybeSync; - pub use std::sync::Arc as MaybeArc; -} - -#[cfg(not(feature = "sync"))] -mod inner { - pub trait MaybeSync {} - impl MaybeSync for T where T: ?Sized {} - pub trait MaybeSend {} - impl MaybeSend for T where T: ?Sized {} - - pub use std::rc::Rc as MaybeArc; -} - -#[allow(clippy::disallowed_types)] -#[allow(dead_code)] // not used for all features -#[inline] -pub fn new_rc(value: T) -> MaybeArc { - MaybeArc::new(value) -} diff --git a/rs_lib/tests/file_fetcher_test.rs b/rs_lib/tests/file_fetcher_test.rs deleted file mode 100644 index 81ddf18..0000000 --- a/rs_lib/tests/file_fetcher_test.rs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -#![allow(clippy::disallowed_methods)] - -use std::rc::Rc; -use std::time::SystemTime; - -use deno_cache_dir::file_fetcher::AuthTokens; -use deno_cache_dir::file_fetcher::CacheSetting; -use deno_cache_dir::file_fetcher::CachedOrRedirect; -use deno_cache_dir::file_fetcher::FetchLocalOptions; -use deno_cache_dir::file_fetcher::FetchNoFollowErrorKind; -use deno_cache_dir::file_fetcher::FetchNoFollowOptions; -use deno_cache_dir::file_fetcher::FileFetcher; -use deno_cache_dir::file_fetcher::FileFetcherOptions; -use deno_cache_dir::file_fetcher::FileOrRedirect; -use deno_cache_dir::file_fetcher::HttpClient; -use deno_cache_dir::file_fetcher::NullBlobStore; -use deno_cache_dir::file_fetcher::NullMemoryFiles; -use deno_cache_dir::file_fetcher::SendError; -use deno_cache_dir::file_fetcher::SendResponse; -use deno_cache_dir::memory::MemoryHttpCache; -use http::HeaderMap; -use sys_traits::FsCreateDirAll; -use sys_traits::FsWrite; -use sys_traits::impls::InMemorySys; -use url::Url; - -#[tokio::test] -async fn test_file_fetcher_redirects() { - #[derive(Debug)] - struct TestHttpClient; - - #[async_trait::async_trait(?Send)] - impl HttpClient for TestHttpClient { - async fn send_no_follow( - &self, - _url: &Url, - _headers: HeaderMap, - ) -> Result { - Ok(SendResponse::Redirect(HeaderMap::new())) - } - } - - let sys = InMemorySys::default(); - let file_fetcher = create_file_fetcher(sys.clone(), TestHttpClient); - let result = file_fetcher - .fetch_no_follow( - &Url::parse("http://localhost/bad_redirect").unwrap(), - FetchNoFollowOptions::default(), - ) - .await; - - match result.unwrap_err().into_kind() { - FetchNoFollowErrorKind::RedirectHeaderParse(err) => { - assert_eq!(err.request_url.as_str(), "http://localhost/bad_redirect"); - } - err => unreachable!("{:?}", err), - } - - let time = SystemTime::now(); - sys.set_time(Some(time)); - sys.fs_create_dir_all("/").unwrap(); - sys.fs_write("/some_path.ts", "text").unwrap(); - - for include_mtime in [true, false] { - let result = file_fetcher - .fetch_no_follow( - &Url::parse("file:///some_path.ts").unwrap(), - FetchNoFollowOptions { - local: FetchLocalOptions { include_mtime }, - ..Default::default() - }, - ) - .await; - match result.unwrap() { - FileOrRedirect::File(file) => { - assert_eq!(file.mtime, if include_mtime { Some(time) } else { None }); - assert_eq!(file.source.to_vec(), b"text"); - } - FileOrRedirect::Redirect(_) => unreachable!(), - } - } -} - -#[tokio::test] -async fn test_file_fetcher_ensure_cached() { - #[derive(Debug)] - struct TestHttpClient; - - #[async_trait::async_trait(?Send)] - impl HttpClient for TestHttpClient { - async fn send_no_follow( - &self, - url: &Url, - _headers: HeaderMap, - ) -> Result { - if url.path() == "/redirect" { - let mut header_map = HeaderMap::new(); - header_map.insert(http::header::LOCATION, "/home".parse().unwrap()); - Ok(SendResponse::Redirect(header_map)) - } else { - Ok(SendResponse::Success( - HeaderMap::new(), - "hello".to_string().into_bytes(), - )) - } - } - } - - let sys = InMemorySys::default(); - let file_fetcher = create_file_fetcher(sys.clone(), TestHttpClient); - { - let result = file_fetcher - .ensure_cached_no_follow( - &Url::parse("http://localhost/redirect").unwrap(), - FetchNoFollowOptions::default(), - ) - .await; - assert_eq!( - result.unwrap(), - CachedOrRedirect::Redirect(Url::parse("http://localhost/home").unwrap()) - ); - } - { - let result = file_fetcher - .ensure_cached_no_follow( - &Url::parse("http://localhost/other").unwrap(), - FetchNoFollowOptions::default(), - ) - .await; - assert_eq!(result.unwrap(), CachedOrRedirect::Cached); - } - - sys.fs_create_dir_all("/").unwrap(); - sys.fs_write("/some_path.ts", "text").unwrap(); - { - let result = file_fetcher - .ensure_cached_no_follow( - &Url::parse("file:///some_path.ts").unwrap(), - FetchNoFollowOptions::default(), - ) - .await; - assert_eq!(result.unwrap(), CachedOrRedirect::Cached); - } - { - let url = Url::parse("file:///not_exists.ts").unwrap(); - let result = file_fetcher - .ensure_cached_no_follow(&url, FetchNoFollowOptions::default()) - .await; - match result.unwrap_err().as_kind() { - FetchNoFollowErrorKind::NotFound(not_found_url) => { - assert_eq!(url, *not_found_url) - } - _ => unreachable!(), - } - } -} - -fn create_file_fetcher( - sys: InMemorySys, - client: TClient, -) -> FileFetcher { - FileFetcher::new( - NullBlobStore, - sys, - Rc::new(MemoryHttpCache::default()), - client, - Rc::new(NullMemoryFiles), - FileFetcherOptions { - allow_remote: true, - cache_setting: CacheSetting::Use, - auth_tokens: AuthTokens::new(None), - }, - ) -} diff --git a/rs_lib/tests/integration_test.rs b/rs_lib/tests/integration_test.rs deleted file mode 100644 index 5f01f6e..0000000 --- a/rs_lib/tests/integration_test.rs +++ /dev/null @@ -1,794 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -#![allow(clippy::disallowed_methods)] - -use std::collections::HashMap; -use std::path::Path; -use std::rc::Rc; - -use deno_cache_dir::CacheReadFileError; -use deno_cache_dir::Checksum; -use deno_cache_dir::GlobalHttpCache; -use deno_cache_dir::GlobalToLocalCopy; -use deno_cache_dir::HttpCache; -use deno_cache_dir::LocalHttpCache; -use deno_cache_dir::LocalLspHttpCache; -use serde_json::json; -use sys_traits::impls::RealSys; -use tempfile::TempDir; -use url::Url; - -fn jsr_url() -> Url { - Url::parse("https://jsr.io/").unwrap() -} - -#[test] -fn test_global_create_cache() { - let dir = TempDir::new().unwrap(); - let cache_path = dir.path().join("foobar"); - // HttpCache should be created lazily on first use: - // when zipping up a local project with no external dependencies - // "$DENO_DIR/remote" is empty. When unzipping such project - // "$DENO_DIR/remote" might not get restored and in situation - // when directory is owned by root we might not be able - // to create that directory. However if it's not needed it - // doesn't make sense to return error in such specific scenarios. - // For more details check issue: - // https://github.com/denoland/deno/issues/5688 - let sys = RealSys; - let cache = GlobalHttpCache::new(sys, cache_path.clone()); - assert!(!cache.dir_path().exists()); - let url = Url::parse("http://example.com/foo/bar.js").unwrap(); - cache.set(&url, Default::default(), b"hello world").unwrap(); - assert!(cache_path.is_dir()); - assert!(cache.local_path_for_url(&url).unwrap().is_file()); -} - -#[test] -fn test_global_get_set() { - let dir = TempDir::new().unwrap(); - let sys = RealSys; - let cache = GlobalHttpCache::new(sys, dir.path().to_path_buf()); - let url = Url::parse("https://deno.land/x/welcome.ts").unwrap(); - let mut headers = HashMap::new(); - headers.insert( - "content-type".to_string(), - "application/javascript".to_string(), - ); - headers.insert("etag".to_string(), "as5625rqdsfb".to_string()); - let content = b"Hello world"; - cache.set(&url, headers, content).unwrap(); - let key = cache.cache_item_key(&url).unwrap(); - let content = String::from_utf8( - cache.get(&key, None).unwrap().unwrap().content.into_owned(), - ) - .unwrap(); - let headers = cache.read_headers(&key).unwrap().unwrap(); - assert_eq!(content, "Hello world"); - assert_eq!( - headers.get("content-type").unwrap(), - "application/javascript" - ); - assert_eq!(headers.get("etag").unwrap(), "as5625rqdsfb"); - assert_eq!(headers.get("foobar"), None); - let download_time = cache.read_download_time(&key).unwrap().unwrap(); - let elapsed = download_time.elapsed().unwrap(); - assert!(elapsed.as_secs() < 2, "Elapsed: {:?}", elapsed); - let matching_checksum = - "64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c"; - // reading with checksum that matches - { - let found_content = cache - .get(&key, Some(Checksum::new(matching_checksum))) - .unwrap() - .unwrap() - .content; - assert_eq!(found_content, content.as_bytes()); - } - // reading with a checksum that doesn't match - { - let not_matching_checksum = "1234"; - let err = cache - .get(&key, Some(Checksum::new(not_matching_checksum))) - .err() - .unwrap(); - let err = match err { - CacheReadFileError::ChecksumIntegrity(err) => err, - _ => unreachable!(), - }; - assert_eq!(err.actual, matching_checksum); - assert_eq!(err.expected, not_matching_checksum); - assert_eq!(err.url, url); - } -} - -#[test] -fn test_local_global_cache() { - let temp_dir = TempDir::new().unwrap(); - let global_cache_path = temp_dir.path().join("global"); - let local_cache_path = temp_dir.path().join("local"); - let sys = RealSys; - let global_cache = - Rc::new(GlobalHttpCache::new(sys, global_cache_path.clone())); - let local_cache = LocalHttpCache::new( - local_cache_path.clone(), - global_cache.clone(), - GlobalToLocalCopy::Allow, - jsr_url(), - ); - - let manifest_file_path = local_cache_path.join("manifest.json"); - // mapped url - { - let url = Url::parse("https://deno.land/x/mod.ts").unwrap(); - let content = "export const test = 5;"; - global_cache - .set( - &url, - HashMap::from([( - "content-type".to_string(), - "application/typescript".to_string(), - )]), - content.as_bytes(), - ) - .unwrap(); - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - let headers = local_cache.read_headers(&key).unwrap().unwrap(); - // won't have any headers because the content-type is derivable from the url - assert_eq!(headers, HashMap::new()); - // no manifest file yet - assert!(!manifest_file_path.exists()); - - // now try deleting the global cache and we should still be able to load it - std::fs::remove_dir_all(&global_cache_path).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - } - - // file that's directly mappable to a url - { - let content = "export const a = 1;"; - std::fs::write(local_cache_path.join("deno.land").join("main.js"), content) - .unwrap(); - - // now we should be able to read this file because it's directly mappable to a url - let url = Url::parse("https://deno.land/main.js").unwrap(); - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - let headers = local_cache.read_headers(&key).unwrap().unwrap(); - assert_eq!(headers, HashMap::new()); - } - - // now try a file with a different content-type header - { - let url = - Url::parse("https://deno.land/x/different_content_type.ts").unwrap(); - let content = "export const test = 5;"; - global_cache - .set( - &url, - HashMap::from([( - "content-type".to_string(), - "application/javascript".to_string(), - )]), - content.as_bytes(), - ) - .unwrap(); - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - let headers = local_cache.read_headers(&key).unwrap().unwrap(); - assert_eq!( - headers, - HashMap::from([( - "content-type".to_string(), - "application/javascript".to_string(), - )]) - ); - assert_eq!( - read_manifest(&manifest_file_path), - json!({ - "modules": { - "https://deno.land/x/different_content_type.ts": { - "headers": { - "content-type": "application/javascript" - } - } - } - }) - ); - // delete the manifest file - std::fs::remove_file(&manifest_file_path).unwrap(); - - // Now try resolving the key again and the content type should still be application/javascript. - // This is maintained because we hash the filename when the headers don't match the extension. - let headers = local_cache.read_headers(&key).unwrap().unwrap(); - assert_eq!( - headers, - HashMap::from([( - "content-type".to_string(), - "application/javascript".to_string(), - )]) - ); - } - - // reset the local cache - std::fs::remove_dir_all(&local_cache_path).unwrap(); - let local_cache = LocalHttpCache::new( - local_cache_path.clone(), - global_cache.clone(), - GlobalToLocalCopy::Allow, - jsr_url(), - ); - - // now try caching a file with many headers - { - let url = Url::parse("https://deno.land/x/my_file.ts").unwrap(); - let content = "export const test = 5;"; - global_cache - .set( - &url, - HashMap::from([ - ( - "content-type".to_string(), - "application/typescript".to_string(), - ), - ("x-typescript-types".to_string(), "./types.d.ts".to_string()), - ("x-deno-warning".to_string(), "Stop right now.".to_string()), - ( - "x-other-header".to_string(), - "Thank you very much.".to_string(), - ), - ]), - content.as_bytes(), - ) - .unwrap(); - let check_output = |local_cache: &LocalHttpCache<_>| { - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - let headers = local_cache.read_headers(&key).unwrap().unwrap(); - assert_eq!( - headers, - HashMap::from([ - ("x-typescript-types".to_string(), "./types.d.ts".to_string(),), - ("x-deno-warning".to_string(), "Stop right now.".to_string(),) - ]) - ); - assert_eq!( - read_manifest(&manifest_file_path), - json!({ - "modules": { - "https://deno.land/x/my_file.ts": { - "headers": { - "x-deno-warning": "Stop right now.", - "x-typescript-types": "./types.d.ts" - } - } - } - }) - ); - }; - check_output(&local_cache); - // now ensure it's the same when re-creating the cache - check_output(&LocalHttpCache::new( - local_cache_path.to_path_buf(), - global_cache.clone(), - GlobalToLocalCopy::Allow, - jsr_url(), - )); - } - - // reset the local cache - std::fs::remove_dir_all(&local_cache_path).unwrap(); - let local_cache = LocalHttpCache::new( - local_cache_path.clone(), - global_cache.clone(), - GlobalToLocalCopy::Allow, - jsr_url(), - ); - - // try a file that can't be mapped to the file system - { - { - let url = Url::parse("https://deno.land/INVALID/Module.ts?dev").unwrap(); - let content = "export const test = 5;"; - global_cache - .set(&url, HashMap::new(), content.as_bytes()) - .unwrap(); - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - let headers = local_cache.read_headers(&key).unwrap().unwrap(); - // won't have any headers because the content-type is derivable from the url - assert_eq!(headers, HashMap::new()); - } - - // now try a file in the same directory, but that maps to the local filesystem - { - let url = Url::parse("https://deno.land/INVALID/module2.ts").unwrap(); - let content = "export const test = 4;"; - global_cache - .set(&url, HashMap::new(), content.as_bytes()) - .unwrap(); - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - assert!( - local_cache_path - .join("deno.land/#invalid_1ee01/module2.ts") - .exists() - ); - - // ensure we can still read this file with a new local cache - let local_cache = LocalHttpCache::new( - local_cache_path.to_path_buf(), - global_cache.clone(), - GlobalToLocalCopy::Allow, - jsr_url(), - ); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - } - - assert_eq!( - read_manifest(&manifest_file_path), - json!({ - "modules": { - "https://deno.land/INVALID/Module.ts?dev": { - } - }, - "folders": { - "https://deno.land/INVALID/": "deno.land/#invalid_1ee01", - } - }) - ); - } - - // reset the local cache - std::fs::remove_dir_all(&local_cache_path).unwrap(); - let local_cache = LocalHttpCache::new( - local_cache_path.clone(), - global_cache.clone(), - GlobalToLocalCopy::Allow, - jsr_url(), - ); - - // now try a redirect - { - let url = Url::parse("https://deno.land/redirect.ts").unwrap(); - global_cache - .set( - &url, - HashMap::from([("location".to_string(), "./x/mod.ts".to_string())]), - "Redirecting to other url...".as_bytes(), - ) - .unwrap(); - let key = local_cache.cache_item_key(&url).unwrap(); - let headers = local_cache.read_headers(&key).unwrap().unwrap(); - assert_eq!( - headers, - HashMap::from([("location".to_string(), "./x/mod.ts".to_string())]) - ); - assert_eq!( - read_manifest(&manifest_file_path), - json!({ - "modules": { - "https://deno.land/redirect.ts": { - "headers": { - "location": "./x/mod.ts" - } - } - } - }) - ); - } - - // reset the local cache - std::fs::remove_dir_all(&local_cache_path).unwrap(); - let local_cache = LocalHttpCache::new( - local_cache_path.clone(), - global_cache.clone(), - GlobalToLocalCopy::Allow, - jsr_url(), - ); - let url = Url::parse("https://deno.land/x/mod.ts").unwrap(); - let matching_checksum = - "5eadcbe625a8489347fc3b229ab66bdbcbdfecedf229dfe5d0a8a399dae6c005"; - let content = "export const test = 5;"; - global_cache - .set( - &url, - HashMap::from([( - "content-type".to_string(), - "application/typescript".to_string(), - )]), - content.as_bytes(), - ) - .unwrap(); - let key = local_cache.cache_item_key(&url).unwrap(); - // reading with a checksum that doesn't match - // (ensure it doesn't match twice so we know it wasn't copied to the local cache) - for _ in 0..2 { - let not_matching_checksum = "1234"; - let err = local_cache - .get(&key, Some(Checksum::new(not_matching_checksum))) - .err() - .unwrap(); - let err = match err { - CacheReadFileError::ChecksumIntegrity(err) => err, - _ => unreachable!(), - }; - assert_eq!(err.actual, matching_checksum); - assert_eq!(err.expected, not_matching_checksum); - assert_eq!(err.url, url); - } - // reading with checksum that matches - { - let found_content = local_cache - .get(&key, Some(Checksum::new(matching_checksum))) - .unwrap() - .unwrap() - .content; - assert_eq!(found_content, content.as_bytes()); - } - // at this point the file should exist in the local cache and so the checksum will be ignored - { - let found_content = local_cache - .get(&key, Some(Checksum::new("not matching"))) - .unwrap() - .unwrap() - .content; - assert_eq!(found_content, content.as_bytes()); - } -} - -fn read_manifest(path: &Path) -> serde_json::Value { - let manifest = std::fs::read_to_string(path).unwrap(); - serde_json::from_str(&manifest).unwrap() -} - -#[test] -fn test_lsp_local_cache() { - let temp_dir = TempDir::new().unwrap(); - let global_cache_path = temp_dir.path().join("global"); - let local_cache_path = temp_dir.path().join("local"); - let sys = RealSys; - let global_cache = - Rc::new(GlobalHttpCache::new(sys, global_cache_path.to_path_buf())); - let local_cache = LocalHttpCache::new( - local_cache_path.to_path_buf(), - global_cache.clone(), - GlobalToLocalCopy::Allow, - jsr_url(), - ); - let create_readonly_cache = || { - LocalLspHttpCache::new(local_cache_path.to_path_buf(), global_cache.clone()) - }; - - // mapped url - { - let url = Url::parse("https://deno.land/x/mod.ts").unwrap(); - let content = "export const test = 5;"; - global_cache - .set( - &url, - HashMap::from([( - "content-type".to_string(), - "application/typescript".to_string(), - )]), - content.as_bytes(), - ) - .unwrap(); - // will be None because it's readonly - { - let readonly_local_cache = create_readonly_cache(); - let key = readonly_local_cache.cache_item_key(&url).unwrap(); - assert_eq!(readonly_local_cache.get(&key, None).unwrap(), None); - } - // populate it with the non-readonly local cache - { - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - } - // now the readonly cache will have it - { - let readonly_local_cache = create_readonly_cache(); - let key = readonly_local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - readonly_local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - } - - { - // check getting the file url works - let readonly_local_cache = create_readonly_cache(); - let file_url = readonly_local_cache.get_file_url(&url); - let expected = Url::from_directory_path(&local_cache_path) - .unwrap() - .join("deno.land/x/mod.ts") - .unwrap(); - assert_eq!(file_url, Some(expected)); - - // get the reverse mapping - let mapping = readonly_local_cache.get_remote_url( - local_cache_path - .join("deno.land") - .join("x") - .join("mod.ts") - .as_path(), - ); - assert_eq!(mapping.as_ref(), Some(&url)); - } - } - - // now try a file with a different content-type header - { - let url = - Url::parse("https://deno.land/x/different_content_type.ts").unwrap(); - let content = "export const test = 5;"; - global_cache - .set( - &url, - HashMap::from([( - "content-type".to_string(), - "application/javascript".to_string(), - )]), - content.as_bytes(), - ) - .unwrap(); - // populate it with the non-readonly local cache - { - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - } - { - let readonly_local_cache = create_readonly_cache(); - let key = readonly_local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - readonly_local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - - let file_url = readonly_local_cache.get_file_url(&url).unwrap(); - let path = file_url.to_file_path().unwrap(); - assert!(path.exists()); - let mapping = readonly_local_cache.get_remote_url(&path); - assert_eq!(mapping.as_ref(), Some(&url)); - } - } - - // try http specifiers that can't be mapped to the file system - { - let urls = [ - "http://deno.land/INVALID/Module.ts?dev", - "http://deno.land/INVALID/SubDir/Module.ts?dev", - ]; - for url in urls { - let url = Url::parse(url).unwrap(); - let content = "export const test = 5;"; - global_cache - .set(&url, HashMap::new(), content.as_bytes()) - .unwrap(); - // populate it with the non-readonly local cache - { - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - } - { - let readonly_local_cache = create_readonly_cache(); - let key = readonly_local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - readonly_local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - - let file_url = readonly_local_cache.get_file_url(&url).unwrap(); - let path = file_url.to_file_path().unwrap(); - assert!(path.exists()); - let mapping = readonly_local_cache.get_remote_url(&path); - assert_eq!(mapping.as_ref(), Some(&url)); - } - } - - // now try a files in the same and sub directories, that maps to the local filesystem - let urls = [ - "http://deno.land/INVALID/module2.ts", - "http://deno.land/INVALID/SubDir/module3.ts", - "http://deno.land/INVALID/SubDir/sub_dir/module4.ts", - ]; - for url in urls { - let url = Url::parse(url).unwrap(); - let content = "export const test = 4;"; - global_cache - .set(&url, HashMap::new(), content.as_bytes()) - .unwrap(); - // populate it with the non-readonly local cache - { - let key = local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - } - { - let readonly_local_cache = create_readonly_cache(); - let key = readonly_local_cache.cache_item_key(&url).unwrap(); - assert_eq!( - String::from_utf8( - readonly_local_cache - .get(&key, None) - .unwrap() - .unwrap() - .content - .into_owned() - ) - .unwrap(), - content - ); - let file_url = readonly_local_cache.get_file_url(&url).unwrap(); - let path = file_url.to_file_path().unwrap(); - assert!(path.exists()); - let mapping = readonly_local_cache.get_remote_url(&path); - assert_eq!(mapping.as_ref(), Some(&url)); - } - - // ensure we can still get this file with a new local cache - let local_cache = create_readonly_cache(); - let file_url = local_cache.get_file_url(&url).unwrap(); - let path = file_url.to_file_path().unwrap(); - assert!(path.exists()); - let mapping = local_cache.get_remote_url(&path); - assert_eq!(mapping.as_ref(), Some(&url)); - } - } -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 47a730b..cb0a8a1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.91.1" +channel = "1.93.1" components = ["clippy", "rustfmt"] diff --git a/test.ts b/test.ts index 7d4cd99..926eea3 100644 --- a/test.ts +++ b/test.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. import { assertEquals } from "@std/assert"; import { createGraph } from "@deno/graph"; diff --git a/util.ts b/util.ts index 993492f..6cd2f8f 100644 --- a/util.ts +++ b/util.ts @@ -1,4 +1,4 @@ -// Copyright 2018-2025 the Deno authors. MIT license. +// Copyright the Deno authors. MIT license. export const CACHE_PERM = 0o644; From 039950abe6604bef47c9290a75176b472fc3ba17 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 3 Mar 2026 14:56:04 +0100 Subject: [PATCH 2/7] updates --- cache_test.ts | 4 +--- deno_dir.ts | 2 +- file_fetcher.ts | 26 +++++--------------------- file_fetcher_test.ts | 8 ++++---- http_cache.ts | 7 ++----- mod.ts | 10 ++++------ rs_lib/lib.rs | 4 ++-- 7 files changed, 19 insertions(+), 42 deletions(-) diff --git a/cache_test.ts b/cache_test.ts index 8a3c276..a8ce290 100644 --- a/cache_test.ts +++ b/cache_test.ts @@ -12,9 +12,7 @@ async function setup() { }); const denoDir = new DenoDir(tempdir); const fileFetcher = new FileFetcher( - () => { - return denoDir.createHttpCache(); - }, + denoDir.createHttpCache(), "use", true, ); diff --git a/deno_dir.ts b/deno_dir.ts index 55891ca..b1f918a 100644 --- a/deno_dir.ts +++ b/deno_dir.ts @@ -26,7 +26,7 @@ export class DenoDir { createHttpCache( options?: { vendorRoot?: string | URL; readOnly?: boolean }, - ): Promise { + ): HttpCache { return HttpCache.create({ root: join(this.root, "remote"), vendorRoot: options?.vendorRoot == null diff --git a/file_fetcher.ts b/file_fetcher.ts index 2792ec1..d039167 100644 --- a/file_fetcher.ts +++ b/file_fetcher.ts @@ -116,12 +116,10 @@ export class FileFetcher { #authTokens: AuthTokens; #cache = new Map(); #cacheSetting: CacheSetting; - #httpCache: HttpCache | undefined; - #httpCachePromise: Promise | undefined; - #httpCacheFactory: () => Promise; + #httpCache: HttpCache; constructor( - httpCacheFactory: () => Promise, + httpCache: HttpCache, cacheSetting: CacheSetting = "use", allowRemote = true, ) { @@ -129,7 +127,7 @@ export class FileFetcher { this.#authTokens = new AuthTokens(Deno.env.get("DENO_AUTH_TOKENS")); this.#allowRemote = allowRemote; this.#cacheSetting = cacheSetting; - this.#httpCacheFactory = httpCacheFactory; + this.#httpCache = httpCache; } async #fetchBlobDataUrl( @@ -300,7 +298,7 @@ export class FileFetcher { const response = await this.#fetchBlobDataUrl( specifier, this.#resolveOptions(options), - await this.#resolveHttpCache(), + this.#httpCache, ); await this.#cache.set(specifier.toString(), response); return response; @@ -312,7 +310,7 @@ export class FileFetcher { const response = await this.#fetchRemoteOnce( specifier, this.#resolveOptions(options), - await this.#resolveHttpCache(), + this.#httpCache, ); if (response) { await this.#cache.set(specifier.toString(), response); @@ -326,20 +324,6 @@ export class FileFetcher { options.cacheSetting = options.cacheSetting ?? this.#cacheSetting; return options as ResolvedFetchOptions; } - - #resolveHttpCache(): Promise { - if (this.#httpCache != null) { - return Promise.resolve(this.#httpCache); - } - if (!this.#httpCachePromise) { - this.#httpCachePromise = this.#httpCacheFactory().then((cache) => { - this.#httpCache = cache; - this.#httpCachePromise = undefined; - return cache; - }); - } - return this.#httpCachePromise; - } } export async function fetchWithRetries( diff --git a/file_fetcher_test.ts b/file_fetcher_test.ts index dc01254..ebfbfa8 100644 --- a/file_fetcher_test.ts +++ b/file_fetcher_test.ts @@ -8,7 +8,7 @@ Deno.test({ name: "FileFetcher", async fn() { const denoDir = new DenoDir(); - const fileFetcher = new FileFetcher(() => denoDir.createHttpCache()); + const fileFetcher = new FileFetcher(denoDir.createHttpCache()); const graph = await createGraph("https://deno.land/x/oak@v10.5.1/mod.ts", { load(specifier) { return fileFetcher.fetch(new URL(specifier)); @@ -23,7 +23,7 @@ Deno.test({ name: "FileFetcher - bad checksum no cache", async fn() { const denoDir = new DenoDir(); - const fileFetcher = new FileFetcher(() => denoDir.createHttpCache()); + const fileFetcher = new FileFetcher(denoDir.createHttpCache()); { // should error await assertRejects(async () => { @@ -50,7 +50,7 @@ Deno.test({ name: "FileFetcher - bad checksum reload", async fn() { const denoDir = new DenoDir(); - const fileFetcher = new FileFetcher(() => denoDir.createHttpCache()); + const fileFetcher = new FileFetcher(denoDir.createHttpCache()); await assertRejects(async () => { await fileFetcher.fetchOnce( new URL("https://deno.land/x/oak@v10.5.1/mod.ts"), @@ -67,7 +67,7 @@ Deno.test({ name: "FileFetcher - good checksum reload", async fn() { const denoDir = new DenoDir(); - const fileFetcher = new FileFetcher(() => denoDir.createHttpCache()); + const fileFetcher = new FileFetcher(denoDir.createHttpCache()); await fileFetcher.fetchOnce( new URL("https://deno.land/x/oak@v10.5.1/mod.ts"), { diff --git a/http_cache.ts b/http_cache.ts index c77fa36..9537e3f 100644 --- a/http_cache.ts +++ b/http_cache.ts @@ -2,10 +2,7 @@ import { isAbsolute } from "@std/path"; import { assert } from "./util.ts"; -import { - GlobalHttpCache, - LocalHttpCache, -} from "./lib/deno_cache_dir_wasm.js"; +import { GlobalHttpCache, LocalHttpCache } from "./lib/deno_cache_dir_wasm.js"; export interface HttpCacheCreateOptions { root: string; @@ -37,7 +34,7 @@ export class HttpCache implements Disposable { this.#readOnly = readOnly; } - static async create(options: HttpCacheCreateOptions): Promise { + static create(options: HttpCacheCreateOptions): HttpCache { assert(isAbsolute(options.root), "Root must be an absolute path."); if (options.vendorRoot != null) { diff --git a/mod.ts b/mod.ts index 3fddf30..aaeb294 100644 --- a/mod.ts +++ b/mod.ts @@ -98,12 +98,10 @@ export function createCache({ }: CacheOptions = {}): Loader { const denoDir = new DenoDir(root); const fileFetcher = new FileFetcher( - () => { - return denoDir.createHttpCache({ - readOnly, - vendorRoot, - }); - }, + denoDir.createHttpCache({ + readOnly, + vendorRoot, + }), cacheSetting, allowRemote, ); diff --git a/rs_lib/lib.rs b/rs_lib/lib.rs index 53d9a96..435b4a5 100644 --- a/rs_lib/lib.rs +++ b/rs_lib/lib.rs @@ -15,12 +15,12 @@ use sys_traits::impls::wasm_string_to_path; use url::Url; use wasm_bindgen::prelude::*; +use deno_cache_dir::CacheEntry; use deno_cache_dir::CacheReadFileError; use deno_cache_dir::Checksum; -use deno_cache_dir::HttpCache; -use deno_cache_dir::CacheEntry; use deno_cache_dir::GlobalToLocalCopy; use deno_cache_dir::HeadersMap; +use deno_cache_dir::HttpCache; #[wasm_bindgen] pub fn url_to_filename(url: &str) -> Result { From 09ef9bd5a9b4966e4d04ab3fecaeecfdfcb17cda Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 3 Mar 2026 15:02:05 +0100 Subject: [PATCH 3/7] maybe fix ci --- .github/workflows/ci.yml | 47 +++++++---------------------------- .github/workflows/publish.yml | 21 ---------------- .github/workflows/release.yml | 4 +-- 3 files changed, 11 insertions(+), 61 deletions(-) delete mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f17f2e..dddd6aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,48 +3,16 @@ name: ci on: [push, pull_request] jobs: - rust: - name: deno_cache_dir-${{ matrix.os }} - if: | - github.event_name == 'push' || - !startsWith(github.event.pull_request.head.label, 'denoland:') + deno: + name: deno_cache_dir ${{ matrix.os }} runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: matrix: os: [macOS-latest, ubuntu-latest, windows-latest] - - steps: - - name: Clone repository - uses: actions/checkout@v5 - - - name: Install rust - uses: dsherret/rust-toolchain-file@v1 - - - uses: Swatinem/rust-cache@v2 - with: - save-if: ${{ github.ref == 'refs/heads/main' }} - - - name: Format - if: contains(matrix.os, 'ubuntu') - run: cargo fmt --check - - - name: Clippy - if: contains(matrix.os, 'ubuntu') - run: cargo clippy - - - name: Build (sync) - run: cargo build --features sync - - - name: Test - run: cargo test - - deno: - name: deno_cache_dir-deno if: | github.event_name == 'push' || !startsWith(github.event.pull_request.head.label, 'denoland:') - runs-on: ubuntu-latest permissions: contents: read id-token: write @@ -52,7 +20,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install rust uses: dsherret/rust-toolchain-file@v1 - uses: Swatinem/rust-cache@v2 @@ -64,13 +32,16 @@ jobs: deno-version: canary - name: Format - run: deno fmt --check + if: contains(matrix.os, 'ubuntu') + run: cargo fmt --check && deno fmt --check + - name: Clippy + if: contains(matrix.os, 'ubuntu') + run: cargo clippy && deno lint - name: Build run: deno task build - - name: Lint - run: deno lint - name: Test run: deno task test - name: Publish JSR + if: contains(matrix.os, 'ubuntu') run: deno run -A jsr:@david/publish-on-tag@0.1.3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index e05fab2..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: publish - -on: - push: - tags: - - "*" - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - name: Clone repository - uses: actions/checkout@v5 - - uses: rust-lang/crates-io-auth-action@v1 - id: auth - - run: cargo publish -p deno_cache_dir - env: - CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7931ae6..01536f8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,11 +20,11 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: token: ${{ secrets.DENOBOT_PAT }} - - uses: denoland/setup-deno@v1 + - uses: denoland/setup-deno@v2 with: deno-version: canary - uses: dsherret/rust-toolchain-file@v1 From 2f937f8896569640661165dcee2fff5dc42216ef Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 3 Mar 2026 15:03:03 +0100 Subject: [PATCH 4/7] try again --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dddd6aa..c1588a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,6 @@ jobs: permissions: contents: read id-token: write - timeout-minutes: 30 steps: - name: Clone repository From 265279b77d22c6af19443ed5240ab91bc9b927a9 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 3 Mar 2026 15:04:53 +0100 Subject: [PATCH 5/7] maybe fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1588a9..685ad99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: cargo fmt --check && deno fmt --check - name: Clippy if: contains(matrix.os, 'ubuntu') - run: cargo clippy && deno lint + run: cargo clippy --target=wasm32-unknown-unknown && deno lint - name: Build run: deno task build - name: Test From fd7f7bb3d7a86a3619c0e3e1fcc83b55fa2fbbc3 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 3 Mar 2026 15:07:51 +0100 Subject: [PATCH 6/7] update --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 685ad99..3f37933 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,8 @@ jobs: uses: denoland/setup-deno@v2 with: deno-version: canary + - name: Install wasm32-unknown-unknown + run: rustup target add wasm32-unknown-unknown - name: Format if: contains(matrix.os, 'ubuntu') From 0de1af05ad556beeb40dedf96cd8edc2e0ee2868 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 3 Mar 2026 15:10:02 +0100 Subject: [PATCH 7/7] try this --- deno.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deno.json b/deno.json index df62a41..27bcfc7 100644 --- a/deno.json +++ b/deno.json @@ -16,9 +16,7 @@ "Cargo.lock", "rs_lib", "**/*.toml", - "!lib/snippets/", - "!lib/deno_cache_dir.generated.js", - "!lib/deno_cache_dir.generated.d.ts" + "!lib/" ] }, "exclude": ["target"],