diff --git a/Cargo.lock b/Cargo.lock index dc1b0074b8..b0b486bf46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,7 +380,7 @@ dependencies = [ "rustversion", "serde", "sync_wrapper", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] @@ -1650,6 +1650,7 @@ dependencies = [ "dyn-clone", "error_reporter", "fast-socks5", + "h2 0.4.4", "hickory-resolver", "http 1.1.0", "http-body-util", @@ -1666,7 +1667,7 @@ dependencies = [ "tokio-rustls", "tokio-socks", "tokio-util", - "tower", + "tower 0.5.2", "tower-http", "tower-service", ] @@ -1924,7 +1925,7 @@ dependencies = [ [[package]] name = "deno_node" -version = "0.120.0" +version = "0.121.0" dependencies = [ "aead-gcm-stream", "aes", @@ -2321,7 +2322,7 @@ dependencies = [ "serde_json", "tokio", "tokio-util", - "tower", + "tower 0.4.13", "tracing", ] @@ -4551,9 +4552,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libffi" @@ -7975,7 +7976,7 @@ dependencies = [ "socket2", "tokio", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -8001,6 +8002,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-http" version = "0.6.1" @@ -8029,9 +8045,9 @@ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" diff --git a/Cargo.toml b/Cargo.toml index c08988de8c..a2d0685dd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,7 @@ deno_io = { version = "0.93.0", path = "./ext/io" } deno_kv = { version = "0.91.0", path = "./ext/kv" } deno_napi = { version = "0.114.0", path = "./ext/napi" } deno_net = { version = "0.175.0", path = "./ext/net" } -deno_node = { version = "0.120.0", path = "./ext/node" } +deno_node = { version = "0.121.0", path = "./ext/node" } deno_telemetry = { version = "0.5.0", path = "./ext/telemetry" } deno_tls = { version = "0.170.0", path = "./ext/tls" } deno_url = { version = "0.183.0", path = "./ext/url" } @@ -150,7 +150,7 @@ indexmap = { version = "2", features = ["serde"] } ipnet = "2.3" jsonc-parser = { version = "=0.26.2", features = ["serde"] } lazy-regex = "3" -libc = "0.2.126" +libc = "0.2.168" libz-sys = { version = "1.1.20", default-features = false } log = { version = "0.4.20", features = ["kv"] } lsp-types = "=0.97.0" # used by tower-lsp and "proposed" feature is unstable in patch releases @@ -203,7 +203,7 @@ tokio-metrics = { version = "0.3.0", features = ["rt"] } tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring", "tls12"] } tokio-socks = "0.5.1" tokio-util = "0.7.4" -tower = { version = "0.4.13", default-features = false, features = ["util"] } +tower = { version = "0.5.2", default-features = false, features = ["retry", "util"] } tower-http = { version = "0.6.1", features = ["decompression-br", "decompression-gzip"] } tower-lsp = { package = "deno_tower_lsp", version = "0.1.0", features = ["proposed"] } tower-service = "0.3.2" diff --git a/cli/http_util.rs b/cli/http_util.rs index ce05d66b78..b24dd7bc0c 100644 --- a/cli/http_util.rs +++ b/cli/http_util.rs @@ -145,9 +145,7 @@ impl HttpClient { } pub fn get(&self, url: Url) -> Result { - let body = http_body_util::Empty::new() - .map_err(|never| match never {}) - .boxed(); + let body = deno_fetch::ReqBody::empty(); let mut req = http::Request::new(body); *req.uri_mut() = url.as_str().parse()?; Ok(RequestBuilder { @@ -179,9 +177,7 @@ impl HttpClient { S: serde::Serialize, { let json = deno_core::serde_json::to_vec(ser)?; - let body = http_body_util::Full::new(json.into()) - .map_err(|never| match never {}) - .boxed(); + let body = deno_fetch::ReqBody::full(json.into()); let builder = self.post(url, body)?; Ok(builder.header( http::header::CONTENT_TYPE, @@ -194,9 +190,7 @@ impl HttpClient { url: &Url, headers: HeaderMap, ) -> Result, SendError> { - let body = http_body_util::Empty::new() - .map_err(|never| match never {}) - .boxed(); + let body = deno_fetch::ReqBody::empty(); let mut request = http::Request::new(body); *request.uri_mut() = http::Uri::try_from(url.as_str())?; *request.headers_mut() = headers; diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs index 001e401459..45a040d236 100644 --- a/cli/tools/registry/mod.rs +++ b/cli/tools/registry/mod.rs @@ -26,6 +26,7 @@ use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::url::Url; +use deno_runtime::deno_fetch; use deno_terminal::colors; use http_body_util::BodyExt; use serde::Deserialize; @@ -911,9 +912,7 @@ async fn publish_package( package.config ); - let body = http_body_util::Full::new(package.tarball.bytes.clone()) - .map_err(|never| match never {}) - .boxed(); + let body = deno_fetch::ReqBody::full(package.tarball.bytes.clone()); let response = http_client .post(url.parse()?, body)? .header( diff --git a/cli/tools/registry/pm/deps.rs b/cli/tools/registry/pm/deps.rs index e4c38276f7..bb03e97f2d 100644 --- a/cli/tools/registry/pm/deps.rs +++ b/cli/tools/registry/pm/deps.rs @@ -3,7 +3,6 @@ use std::borrow::Cow; use std::collections::HashMap; use std::path::PathBuf; -use std::sync::atomic::AtomicBool; use std::sync::Arc; use deno_ast::ModuleSpecifier; @@ -28,6 +27,7 @@ use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; use deno_semver::package::PackageReqReference; +use deno_semver::Version; use deno_semver::VersionReq; use import_map::ImportMap; use import_map::ImportMapWithDiagnostics; @@ -42,6 +42,7 @@ use crate::jsr::JsrFetchResolver; use crate::module_loader::ModuleLoadPreparer; use crate::npm::CliNpmResolver; use crate::npm::NpmFetchResolver; +use crate::util::sync::AtomicFlag; use super::ConfigUpdater; @@ -447,7 +448,7 @@ pub struct DepManager { pending_changes: Vec, - dependencies_resolved: AtomicBool, + dependencies_resolved: AtomicFlag, module_load_preparer: Arc, // TODO(nathanwhit): probably shouldn't be pub pub(crate) jsr_fetch_resolver: Arc, @@ -489,7 +490,7 @@ impl DepManager { resolved_versions: Vec::new(), latest_versions: Vec::new(), jsr_fetch_resolver, - dependencies_resolved: AtomicBool::new(false), + dependencies_resolved: AtomicFlag::lowered(), module_load_preparer, npm_fetch_resolver, npm_resolver, @@ -530,10 +531,7 @@ impl DepManager { } async fn run_dependency_resolution(&self) -> Result<(), AnyError> { - if self - .dependencies_resolved - .load(std::sync::atomic::Ordering::Relaxed) - { + if self.dependencies_resolved.is_raised() { return Ok(()); } @@ -556,9 +554,7 @@ impl DepManager { } DepKind::Jsr => graph.packages.mappings().contains_key(&dep.req), }) { - self - .dependencies_resolved - .store(true, std::sync::atomic::Ordering::Relaxed); + self.dependencies_resolved.raise(); graph_permit.commit(); return Ok(()); } @@ -613,6 +609,7 @@ impl DepManager { ) .await?; + self.dependencies_resolved.raise(); graph_permit.commit(); Ok(()) @@ -655,10 +652,6 @@ impl DepManager { if self.latest_versions.len() == self.deps.len() { return Ok(self.latest_versions.clone()); } - let latest_tag_req = deno_semver::VersionReq::from_raw_text_and_inner( - "latest".into(), - deno_semver::RangeSetOrTag::Tag("latest".into()), - ); let mut latest_versions = Vec::with_capacity(self.deps.len()); let npm_sema = Semaphore::new(32); @@ -670,14 +663,25 @@ impl DepManager { DepKind::Npm => futs.push_back( async { let semver_req = &dep.req; - let latest_req = PackageReq { - name: dep.req.name.clone(), - version_req: latest_tag_req.clone(), - }; let _permit = npm_sema.acquire().await; let semver_compatible = self.npm_fetch_resolver.req_to_nv(semver_req).await; - let latest = self.npm_fetch_resolver.req_to_nv(&latest_req).await; + let info = + self.npm_fetch_resolver.package_info(&semver_req.name).await; + let latest = info + .and_then(|info| { + let latest_tag = info.dist_tags.get("latest")?; + let lower_bound = &semver_compatible.as_ref()?.version; + if latest_tag > lower_bound { + Some(latest_tag.clone()) + } else { + latest_version(Some(latest_tag), info.versions.keys()) + } + }) + .map(|version| PackageNv { + name: semver_req.name.clone(), + version, + }); PackageLatestVersion { latest, semver_compatible, @@ -688,14 +692,29 @@ impl DepManager { DepKind::Jsr => futs.push_back( async { let semver_req = &dep.req; - let latest_req = PackageReq { - name: dep.req.name.clone(), - version_req: deno_semver::WILDCARD_VERSION_REQ.clone(), - }; let _permit = jsr_sema.acquire().await; let semver_compatible = self.jsr_fetch_resolver.req_to_nv(semver_req).await; - let latest = self.jsr_fetch_resolver.req_to_nv(&latest_req).await; + let info = + self.jsr_fetch_resolver.package_info(&semver_req.name).await; + let latest = info + .and_then(|info| { + let lower_bound = &semver_compatible.as_ref()?.version; + latest_version( + Some(lower_bound), + info.versions.iter().filter_map(|(version, version_info)| { + if !version_info.yanked { + Some(version) + } else { + None + } + }), + ) + }) + .map(|version| PackageNv { + name: semver_req.name.clone(), + version, + }); PackageLatestVersion { latest, semver_compatible, @@ -893,3 +912,18 @@ fn parse_req_reference( DepKind::Jsr => JsrPackageReqReference::from_str(input)?.into_inner(), }) } + +fn latest_version<'a>( + start: Option<&Version>, + versions: impl IntoIterator, +) -> Option { + let mut best = start; + for version in versions { + match best { + Some(best_version) if version > best_version => best = Some(version), + None => best = Some(version), + _ => {} + } + } + best.cloned() +} diff --git a/cli/tools/task.rs b/cli/tools/task.rs index 25d1d66710..ec9b238847 100644 --- a/cli/tools/task.rs +++ b/cli/tools/task.rs @@ -94,7 +94,7 @@ pub async fn execute_script( return Ok(0); }; - let task_regex = arg_to_regex(task_name)?; + let task_name_filter = arg_to_task_name_filter(task_name)?; let mut packages_task_info: Vec = vec![]; for folder in workspace.config_folders() { @@ -137,12 +137,20 @@ pub async fn execute_script( // Match tasks in deno.json for name in tasks_config.task_names() { - if task_regex.is_match(name) && !visited.contains(name) { + let matches_filter = match &task_name_filter { + TaskNameFilter::Exact(n) => *n == name, + TaskNameFilter::Regex(re) => re.is_match(name), + }; + if matches_filter && !visited.contains(name) { matched.insert(name.to_string()); visit_task(&tasks_config, &mut visited, name); } } + if matched.is_empty() { + continue; + } + packages_task_info.push(PackageTaskInfo { matched_tasks: matched .iter() @@ -902,3 +910,41 @@ fn strip_ansi_codes_and_escape_control_chars(s: &str) -> String { }) .collect() } + +fn arg_to_task_name_filter(input: &str) -> Result { + if !input.contains("*") { + return Ok(TaskNameFilter::Exact(input)); + } + + let mut regex_str = regex::escape(input); + regex_str = regex_str.replace("\\*", ".*"); + let re = Regex::new(®ex_str)?; + Ok(TaskNameFilter::Regex(re)) +} + +#[derive(Debug)] +enum TaskNameFilter<'s> { + Exact(&'s str), + Regex(regex::Regex), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_arg_to_task_name_filter() { + assert!(matches!( + arg_to_task_name_filter("test").unwrap(), + TaskNameFilter::Exact("test") + )); + assert!(matches!( + arg_to_task_name_filter("test-").unwrap(), + TaskNameFilter::Exact("test-") + )); + assert!(matches!( + arg_to_task_name_filter("test*").unwrap(), + TaskNameFilter::Regex(_) + )); + } +} diff --git a/ext/fetch/Cargo.toml b/ext/fetch/Cargo.toml index 98d2fdf5da..fee21808e7 100644 --- a/ext/fetch/Cargo.toml +++ b/ext/fetch/Cargo.toml @@ -23,6 +23,7 @@ deno_permissions.workspace = true deno_tls.workspace = true dyn-clone = "1" error_reporter = "1" +h2.workspace = true hickory-resolver.workspace = true http.workspace = true http-body-util.workspace = true diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 919c6d3044..103698b3bf 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -10,6 +10,7 @@ use std::borrow::Cow; use std::cell::RefCell; use std::cmp::min; use std::convert::From; +use std::future; use std::path::Path; use std::path::PathBuf; use std::pin::Pin; @@ -66,6 +67,7 @@ use http::header::USER_AGENT; use http::Extensions; use http::Method; use http::Uri; +use http_body_util::combinators::BoxBody; use http_body_util::BodyExt; use hyper::body::Frame; use hyper_util::client::legacy::connect::HttpConnector; @@ -75,6 +77,7 @@ use hyper_util::rt::TokioExecutor; use hyper_util::rt::TokioTimer; use serde::Deserialize; use serde::Serialize; +use tower::retry; use tower::ServiceExt; use tower_http::decompression::Decompression; @@ -476,9 +479,7 @@ where // If a body is passed, we use it, and don't return a body for streaming. con_len = Some(data.len() as u64); - http_body_util::Full::new(data.to_vec().into()) - .map_err(|never| match never {}) - .boxed() + ReqBody::full(data.to_vec().into()) } (_, Some(resource)) => { let resource = state @@ -491,7 +492,7 @@ where } _ => {} } - ReqBody::new(ResourceToBodyAdapter::new(resource)) + ReqBody::streaming(ResourceToBodyAdapter::new(resource)) } (None, None) => unreachable!(), } @@ -501,9 +502,7 @@ where if matches!(method, Method::POST | Method::PUT) { con_len = Some(0); } - http_body_util::Empty::new() - .map_err(|never| match never {}) - .boxed() + ReqBody::empty() }; let mut request = http::Request::new(body); @@ -1066,7 +1065,8 @@ pub fn create_http_client( } let pooled_client = builder.build(connector); - let decompress = Decompression::new(pooled_client).gzip(true).br(true); + let retry_client = retry::Retry::new(FetchRetry, pooled_client); + let decompress = Decompression::new(retry_client).gzip(true).br(true); Ok(Client { inner: decompress, @@ -1083,7 +1083,12 @@ pub fn op_utf8_to_byte_string(#[string] input: String) -> ByteString { #[derive(Clone, Debug)] pub struct Client { - inner: Decompression>, + inner: Decompression< + retry::Retry< + FetchRetry, + hyper_util::client::legacy::Client, + >, + >, // Used to check whether to include a proxy-authorization header proxies: Arc, user_agent: HeaderValue, @@ -1174,10 +1179,70 @@ impl Client { } } -pub type ReqBody = - http_body_util::combinators::BoxBody; -pub type ResBody = - http_body_util::combinators::BoxBody; +// This is a custom enum to allow the retry policy to clone the variants that could be retried. +pub enum ReqBody { + Full(http_body_util::Full), + Empty(http_body_util::Empty), + Streaming(BoxBody), +} + +pub type ResBody = BoxBody; + +impl ReqBody { + pub fn full(bytes: Bytes) -> Self { + ReqBody::Full(http_body_util::Full::new(bytes)) + } + + pub fn empty() -> Self { + ReqBody::Empty(http_body_util::Empty::new()) + } + + pub fn streaming(body: B) -> Self + where + B: hyper::body::Body + + Send + + Sync + + 'static, + { + ReqBody::Streaming(BoxBody::new(body)) + } +} + +impl hyper::body::Body for ReqBody { + type Data = Bytes; + type Error = deno_core::error::AnyError; + + fn poll_frame( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + match &mut *self { + ReqBody::Full(ref mut b) => { + Pin::new(b).poll_frame(cx).map_err(|never| match never {}) + } + ReqBody::Empty(ref mut b) => { + Pin::new(b).poll_frame(cx).map_err(|never| match never {}) + } + ReqBody::Streaming(ref mut b) => Pin::new(b).poll_frame(cx), + } + } + + fn is_end_stream(&self) -> bool { + match self { + ReqBody::Full(ref b) => b.is_end_stream(), + ReqBody::Empty(ref b) => b.is_end_stream(), + ReqBody::Streaming(ref b) => b.is_end_stream(), + } + } + + fn size_hint(&self) -> hyper::body::SizeHint { + match self { + ReqBody::Full(ref b) => b.size_hint(), + ReqBody::Empty(ref b) => b.size_hint(), + ReqBody::Streaming(ref b) => b.size_hint(), + } + } +} /// Copied from https://github.com/seanmonstar/reqwest/blob/b9d62a0323d96f11672a61a17bf8849baec00275/src/async_impl/request.rs#L572 /// Check the request URL for a "username:password" type authority, and if @@ -1214,3 +1279,102 @@ pub fn extract_authority(url: &mut Url) -> Option<(String, Option)> { fn op_fetch_promise_is_settled(promise: v8::Local) -> bool { promise.state() != v8::PromiseState::Pending } + +/// Deno.fetch's retry policy. +#[derive(Clone, Debug)] +struct FetchRetry; + +/// Marker extension that a request has been retried once. +#[derive(Clone, Debug)] +struct Retried; + +impl + retry::Policy, http::Response, E> + for FetchRetry +where + E: std::error::Error + 'static, +{ + /// Don't delay retries. + type Future = future::Ready<()>; + + fn retry( + &mut self, + req: &mut http::Request, + result: &mut Result, E>, + ) -> Option { + if req.extensions().get::().is_some() { + // only retry once + return None; + } + + match result { + Ok(..) => { + // never retry a Response + None + } + Err(err) => { + if is_error_retryable(&*err) { + req.extensions_mut().insert(Retried); + Some(future::ready(())) + } else { + None + } + } + } + } + + fn clone_request( + &mut self, + req: &http::Request, + ) -> Option> { + let body = match req.body() { + ReqBody::Full(b) => ReqBody::Full(b.clone()), + ReqBody::Empty(b) => ReqBody::Empty(*b), + ReqBody::Streaming(..) => return None, + }; + + let mut clone = http::Request::new(body); + *clone.method_mut() = req.method().clone(); + *clone.uri_mut() = req.uri().clone(); + *clone.headers_mut() = req.headers().clone(); + *clone.extensions_mut() = req.extensions().clone(); + Some(clone) + } +} + +fn is_error_retryable(err: &(dyn std::error::Error + 'static)) -> bool { + // Note: hyper doesn't promise it will always be this h2 version. Keep up to date. + if let Some(err) = find_source::(err) { + // They sent us a graceful shutdown, try with a new connection! + if err.is_go_away() + && err.is_remote() + && err.reason() == Some(h2::Reason::NO_ERROR) + { + return true; + } + + // REFUSED_STREAM was sent from the server, which is safe to retry. + // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.7-3.2 + if err.is_reset() + && err.is_remote() + && err.reason() == Some(h2::Reason::REFUSED_STREAM) + { + return true; + } + } + + false +} + +fn find_source<'a, E: std::error::Error + 'static>( + err: &'a (dyn std::error::Error + 'static), +) -> Option<&'a E> { + let mut err = Some(err); + while let Some(src) = err { + if let Some(found) = src.downcast_ref::() { + return Some(found); + } + err = src.source(); + } + None +} diff --git a/ext/fetch/tests.rs b/ext/fetch/tests.rs index 3da29f8aa7..243b80bd90 100644 --- a/ext/fetch/tests.rs +++ b/ext/fetch/tests.rs @@ -133,11 +133,7 @@ async fn rust_test_client_with_resolver( let req = http::Request::builder() .uri(format!("https://{}/foo", src_addr)) - .body( - http_body_util::Empty::new() - .map_err(|err| match err {}) - .boxed(), - ) + .body(crate::ReqBody::empty()) .unwrap(); let resp = client.send(req).await.unwrap(); assert_eq!(resp.status(), http::StatusCode::OK); diff --git a/ext/kv/remote.rs b/ext/kv/remote.rs index 1830aa67ee..891786e319 100644 --- a/ext/kv/remote.rs +++ b/ext/kv/remote.rs @@ -122,9 +122,7 @@ impl RemoteTransport for FetchClient { headers: http::HeaderMap, body: Bytes, ) -> Result<(Url, http::StatusCode, Self::Response), anyhow::Error> { - let body = http_body_util::Full::new(body) - .map_err(|never| match never {}) - .boxed(); + let body = deno_fetch::ReqBody::full(body); let mut req = http::Request::new(body); *req.method_mut() = http::Method::POST; *req.uri_mut() = url.as_str().parse()?; diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index 8ba1141ec1..127633a09b 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_node" -version = "0.120.0" +version = "0.121.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/node/ops/os/cpus.rs b/ext/node/ops/os/cpus.rs index 3f5f430f65..2b931884c3 100644 --- a/ext/node/ops/os/cpus.rs +++ b/ext/node/ops/os/cpus.rs @@ -73,12 +73,17 @@ pub fn cpu_info() -> Option> { cpu_speed = 2_400_000_000; } + extern "C" { + fn mach_host_self() -> std::ffi::c_uint; + static mut mach_task_self_: std::ffi::c_uint; + } + let mut num_cpus: libc::natural_t = 0; let mut info: *mut libc::processor_cpu_load_info_data_t = std::ptr::null_mut(); let mut msg_type: libc::mach_msg_type_number_t = 0; if libc::host_processor_info( - libc::mach_host_self(), + mach_host_self(), libc::PROCESSOR_CPU_LOAD_INFO, &mut num_cpus, &mut info as *mut _ as *mut libc::processor_info_array_t, @@ -111,7 +116,7 @@ pub fn cpu_info() -> Option> { } libc::vm_deallocate( - libc::mach_task_self(), + mach_task_self_, info.as_ptr() as libc::vm_address_t, msg_type as _, ); diff --git a/ext/node/polyfill.rs b/ext/node/polyfill.rs index 762e32f7ba..42cc794955 100644 --- a/ext/node/polyfill.rs +++ b/ext/node/polyfill.rs @@ -67,9 +67,9 @@ generate_builtin_node_module_lists! { "process", "punycode", "querystring", - "repl", "readline", "readline/promises", + "repl", "stream", "stream/consumers", "stream/promises", @@ -90,3 +90,10 @@ generate_builtin_node_module_lists! { "worker_threads", "zlib", } + +#[test] +fn test_builtins_are_sorted() { + let mut builtins_list = SUPPORTED_BUILTIN_NODE_MODULES.to_vec(); + builtins_list.sort(); + assert_eq!(SUPPORTED_BUILTIN_NODE_MODULES, builtins_list); +} diff --git a/ext/node/polyfills/_fs/_fs_access.ts b/ext/node/polyfills/_fs/_fs_access.ts index b501bcbcae..824386e64b 100644 --- a/ext/node/polyfills/_fs/_fs_access.ts +++ b/ext/node/polyfills/_fs/_fs_access.ts @@ -30,50 +30,58 @@ export function access( mode = getValidMode(mode, "access"); const cb = makeCallback(callback); - Deno.lstat(path).then((info) => { - if (info.mode === null) { - // If the file mode is unavailable, we pretend it has - // the permission - cb(null); - return; - } - const m = +mode || 0; - let fileMode = +info.mode || 0; - if (Deno.build.os !== "windows" && info.uid === Deno.uid()) { - // If the user is the owner of the file, then use the owner bits of - // the file permission - fileMode >>= 6; - } - // TODO(kt3k): Also check the case when the user belong to the group - // of the file - if ((m & fileMode) === m) { - // all required flags exist - cb(null); - } else { - // some required flags don't - // deno-lint-ignore no-explicit-any - const e: any = new Error(`EACCES: permission denied, access '${path}'`); - e.path = path; - e.syscall = "access"; - e.errno = codeMap.get("EACCES"); - e.code = "EACCES"; - cb(e); - } - }, (err) => { - if (err instanceof Deno.errors.NotFound) { - // deno-lint-ignore no-explicit-any - const e: any = new Error( - `ENOENT: no such file or directory, access '${path}'`, - ); - e.path = path; - e.syscall = "access"; - e.errno = codeMap.get("ENOENT"); - e.code = "ENOENT"; - cb(e); - } else { - cb(err); - } - }); + Deno.lstat(path).then( + (info) => { + if (info.mode === null) { + // If the file mode is unavailable, we pretend it has + // the permission + cb(null); + return; + } + let m = +mode || 0; + let fileMode = +info.mode || 0; + + if (Deno.build.os === "windows") { + m &= ~fs.X_OK; // Ignore the X_OK bit on Windows + } else if (info.uid === Deno.uid()) { + // If the user is the owner of the file, then use the owner bits of + // the file permission + fileMode >>= 6; + } + + // TODO(kt3k): Also check the case when the user belong to the group + // of the file + + if ((m & fileMode) === m) { + // all required flags exist + cb(null); + } else { + // some required flags don't + // deno-lint-ignore no-explicit-any + const e: any = new Error(`EACCES: permission denied, access '${path}'`); + e.path = path; + e.syscall = "access"; + e.errno = codeMap.get("EACCES"); + e.code = "EACCES"; + cb(e); + } + }, + (err) => { + if (err instanceof Deno.errors.NotFound) { + // deno-lint-ignore no-explicit-any + const e: any = new Error( + `ENOENT: no such file or directory, access '${path}'`, + ); + e.path = path; + e.syscall = "access"; + e.errno = codeMap.get("ENOENT"); + e.code = "ENOENT"; + cb(e); + } else { + cb(err); + } + }, + ); } export const accessPromise = promisify(access) as ( @@ -91,9 +99,11 @@ export function accessSync(path: string | Buffer | URL, mode?: number) { // the permission return; } - const m = +mode! || 0; + let m = +mode! || 0; let fileMode = +info.mode! || 0; - if (Deno.build.os !== "windows" && info.uid === Deno.uid()) { + if (Deno.build.os === "windows") { + m &= ~fs.X_OK; // Ignore the X_OK bit on Windows + } else if (info.uid === Deno.uid()) { // If the user is the owner of the file, then use the owner bits of // the file permission fileMode >>= 6; diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index b8ebc88bed..71169217a7 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -424,8 +424,11 @@ fn rss() -> usize { let mut count = libc::MACH_TASK_BASIC_INFO_COUNT; // SAFETY: libc calls let r = unsafe { + extern "C" { + static mut mach_task_self_: std::ffi::c_uint; + } libc::task_info( - libc::mach_task_self(), + mach_task_self_, libc::MACH_TASK_BASIC_INFO, task_info.as_mut_ptr() as libc::task_info_t, &mut count as *mut libc::mach_msg_type_number_t, diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs index d1f133d3d2..508bcb7bb0 100644 --- a/runtime/ops/web_worker/sync_fetch.rs +++ b/runtime/ops/web_worker/sync_fetch.rs @@ -104,11 +104,7 @@ pub fn op_worker_sync_fetch( let (body, mime_type, res_url) = match script_url.scheme() { "http" | "https" => { - let mut req = http::Request::new( - http_body_util::Empty::new() - .map_err(|never| match never {}) - .boxed(), - ); + let mut req = http::Request::new(deno_fetch::ReqBody::empty()); *req.uri_mut() = script_url.as_str().parse()?; let resp = diff --git a/runtime/sys_info.rs b/runtime/sys_info.rs index cffc90e9da..f99cfc99f9 100644 --- a/runtime/sys_info.rs +++ b/runtime/sys_info.rs @@ -278,11 +278,15 @@ pub fn mem_info() -> Option { mem_info.swap_total = xs.xsu_total; mem_info.swap_free = xs.xsu_avail; + extern "C" { + fn mach_host_self() -> std::ffi::c_uint; + } + let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _; let mut stat = std::mem::zeroed::(); if libc::host_statistics64( // TODO(@littledivy): Put this in a once_cell. - libc::mach_host_self(), + mach_host_self(), libc::HOST_VM_INFO64, &mut stat as *mut libc::vm_statistics64 as *mut _, &mut count, diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 568cad44b0..56c060d958 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -8609,9 +8609,9 @@ fn lsp_completions_node_specifier() { "node:process", "node:punycode", "node:querystring", - "node:repl", "node:readline", "node:readline/promises", + "node:repl", "node:stream", "node:stream/consumers", "node:stream/promises", diff --git a/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.1/mod.ts b/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.1/mod.ts new file mode 100644 index 0000000000..6a8018af41 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.1/mod.ts @@ -0,0 +1 @@ +export const foo = 1; \ No newline at end of file diff --git a/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.1_meta.json b/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.1_meta.json new file mode 100644 index 0000000000..6c213a9c05 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.1_meta.json @@ -0,0 +1,5 @@ +{ + "exports": { + ".": "mod.ts" + } +} diff --git a/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.2/mod.ts b/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.2/mod.ts new file mode 100644 index 0000000000..6a8018af41 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.2/mod.ts @@ -0,0 +1 @@ +export const foo = 1; \ No newline at end of file diff --git a/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.2_meta.json b/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.2_meta.json new file mode 100644 index 0000000000..6c213a9c05 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-only-pre-release/2.0.0-beta.2_meta.json @@ -0,0 +1,5 @@ +{ + "exports": { + ".": "mod.ts" + } +} diff --git a/tests/registry/jsr/@denotest/has-only-pre-release/meta.json b/tests/registry/jsr/@denotest/has-only-pre-release/meta.json new file mode 100644 index 0000000000..cce8d0954f --- /dev/null +++ b/tests/registry/jsr/@denotest/has-only-pre-release/meta.json @@ -0,0 +1,6 @@ +{ + "versions": { + "2.0.0-beta.1": {}, + "2.0.0-beta.2": {} + } +} diff --git a/tests/registry/jsr/@denotest/has-pre-release/1.0.0/mod.ts b/tests/registry/jsr/@denotest/has-pre-release/1.0.0/mod.ts new file mode 100644 index 0000000000..6a8018af41 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-pre-release/1.0.0/mod.ts @@ -0,0 +1 @@ +export const foo = 1; \ No newline at end of file diff --git a/tests/registry/jsr/@denotest/has-pre-release/1.0.0_meta.json b/tests/registry/jsr/@denotest/has-pre-release/1.0.0_meta.json new file mode 100644 index 0000000000..c5807f588c --- /dev/null +++ b/tests/registry/jsr/@denotest/has-pre-release/1.0.0_meta.json @@ -0,0 +1,3 @@ +{ + "exports": {} +} diff --git a/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.1/mod.ts b/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.1/mod.ts new file mode 100644 index 0000000000..6a8018af41 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.1/mod.ts @@ -0,0 +1 @@ +export const foo = 1; \ No newline at end of file diff --git a/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.1_meta.json b/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.1_meta.json new file mode 100644 index 0000000000..6c213a9c05 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.1_meta.json @@ -0,0 +1,5 @@ +{ + "exports": { + ".": "mod.ts" + } +} diff --git a/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.2/mod.ts b/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.2/mod.ts new file mode 100644 index 0000000000..6a8018af41 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.2/mod.ts @@ -0,0 +1 @@ +export const foo = 1; \ No newline at end of file diff --git a/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.2_meta.json b/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.2_meta.json new file mode 100644 index 0000000000..6c213a9c05 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-pre-release/2.0.0-beta.2_meta.json @@ -0,0 +1,5 @@ +{ + "exports": { + ".": "mod.ts" + } +} diff --git a/tests/registry/jsr/@denotest/has-pre-release/meta.json b/tests/registry/jsr/@denotest/has-pre-release/meta.json new file mode 100644 index 0000000000..ba7bc452d7 --- /dev/null +++ b/tests/registry/jsr/@denotest/has-pre-release/meta.json @@ -0,0 +1,7 @@ +{ + "versions": { + "1.0.0": {}, + "2.0.0-beta.1": {}, + "2.0.0-beta.2": {} + } +} diff --git a/tests/registry/npm/@denotest/has-pre-release/1.0.0/package.json b/tests/registry/npm/@denotest/has-pre-release/1.0.0/package.json new file mode 100644 index 0000000000..027b783d54 --- /dev/null +++ b/tests/registry/npm/@denotest/has-pre-release/1.0.0/package.json @@ -0,0 +1,7 @@ +{ + "name": "@denotest/has-pre-release", + "version": "1.0.0", + "publishConfig": { + "tag": "latest" + } +} \ No newline at end of file diff --git a/tests/registry/npm/@denotest/has-pre-release/2.0.0-beta.1/package.json b/tests/registry/npm/@denotest/has-pre-release/2.0.0-beta.1/package.json new file mode 100644 index 0000000000..b3d3b3e634 --- /dev/null +++ b/tests/registry/npm/@denotest/has-pre-release/2.0.0-beta.1/package.json @@ -0,0 +1,4 @@ +{ + "name": "@denotest/has-pre-release", + "version": "2.0.0-beta.1" +} \ No newline at end of file diff --git a/tests/registry/npm/@denotest/has-pre-release/2.0.0-beta.2/package.json b/tests/registry/npm/@denotest/has-pre-release/2.0.0-beta.2/package.json new file mode 100644 index 0000000000..b6f8ea4c76 --- /dev/null +++ b/tests/registry/npm/@denotest/has-pre-release/2.0.0-beta.2/package.json @@ -0,0 +1,4 @@ +{ + "name": "@denotest/has-pre-release", + "version": "2.0.0-beta.2" +} \ No newline at end of file diff --git a/tests/specs/task/workspace_regex_match/__test__.jsonc b/tests/specs/task/workspace_regex_match/__test__.jsonc new file mode 100644 index 0000000000..258c288d44 --- /dev/null +++ b/tests/specs/task/workspace_regex_match/__test__.jsonc @@ -0,0 +1,11 @@ +{ + "tempDir": true, + "tests": { + // Regression test for https://github.com/denoland/deno/issues/27370 + "root": { + "args": "task test-all", + "output": "root.out", + "exitCode": 0 + } + } +} diff --git a/tests/specs/task/workspace_regex_match/deno.json b/tests/specs/task/workspace_regex_match/deno.json new file mode 100644 index 0000000000..ce040ba5ab --- /dev/null +++ b/tests/specs/task/workspace_regex_match/deno.json @@ -0,0 +1,6 @@ +{ + "workspace": ["./subdir"], + "tasks": { + "test-all": "deno task --recursive test" + } +} diff --git a/tests/specs/task/workspace_regex_match/root.out b/tests/specs/task/workspace_regex_match/root.out new file mode 100644 index 0000000000..9da724a5c0 --- /dev/null +++ b/tests/specs/task/workspace_regex_match/root.out @@ -0,0 +1,3 @@ +Task test-all deno task --recursive test +Task test echo 'ok' +ok diff --git a/tests/specs/task/workspace_regex_match/subdir/deno.json b/tests/specs/task/workspace_regex_match/subdir/deno.json new file mode 100644 index 0000000000..78d768e396 --- /dev/null +++ b/tests/specs/task/workspace_regex_match/subdir/deno.json @@ -0,0 +1,5 @@ +{ + "tasks": { + "test": "echo 'ok'" + } +} diff --git a/tests/specs/update/pre_release/__test__.jsonc b/tests/specs/update/pre_release/__test__.jsonc new file mode 100644 index 0000000000..7e2ea39dab --- /dev/null +++ b/tests/specs/update/pre_release/__test__.jsonc @@ -0,0 +1,21 @@ +{ + "tempDir": true, + "steps": [ + { + "args": "i", + "output": "[WILDCARD]" + }, + { + "args": "outdated", + "output": "outdated.out" + }, + { + "args": "outdated --compatible", + "output": "outdated.out" + }, + { + "args": "outdated --update --latest", + "output": "update.out" + } + ] +} diff --git a/tests/specs/update/pre_release/deno.json b/tests/specs/update/pre_release/deno.json new file mode 100644 index 0000000000..07646b5059 --- /dev/null +++ b/tests/specs/update/pre_release/deno.json @@ -0,0 +1,7 @@ +{ + "imports": { + "@denotest/npm-has-pre-release": "npm:@denotest/has-pre-release@^2.0.0-beta.1", + "@denotest/jsr-has-pre-release": "jsr:@denotest/has-pre-release@^2.0.0-beta.1", + "@denotest/has-only-pre-release": "jsr:@denotest/has-only-pre-release@^2.0.0-beta.1" + } +} diff --git a/tests/specs/update/pre_release/deno.lock b/tests/specs/update/pre_release/deno.lock new file mode 100644 index 0000000000..33b136dd53 --- /dev/null +++ b/tests/specs/update/pre_release/deno.lock @@ -0,0 +1,28 @@ +{ + "version": "4", + "specifiers": { + "jsr:@denotest/has-only-pre-release@^2.0.0-beta.1": "2.0.0-beta.1", + "jsr:@denotest/has-pre-release@^2.0.0-beta.1": "2.0.0-beta.1", + "npm:@denotest/has-pre-release@^2.0.0-beta.1": "2.0.0-beta.1" + }, + "jsr": { + "@denotest/has-only-pre-release@2.0.0-beta.1": { + "integrity": "43fd680ea94bb5db5fe1a2d86101c47d0e2cc77323b881755cea9a0372e49537" + }, + "@denotest/has-pre-release@2.0.0-beta.1": { + "integrity": "43fd680ea94bb5db5fe1a2d86101c47d0e2cc77323b881755cea9a0372e49537" + } + }, + "npm": { + "@denotest/has-pre-release@2.0.0-beta.1": { + "integrity": "sha512-K1fHe1L2EUSLgijtzzALNpkkIO0SrX3z+IXvVjjOIE8HKd4T7lkpzDdoUp+WllwS3KXmuJh+9vIfY5lFp38pew==" + } + }, + "workspace": { + "dependencies": [ + "jsr:@denotest/has-only-pre-release@^2.0.0-beta.1", + "jsr:@denotest/has-pre-release@^2.0.0-beta.1", + "npm:@denotest/has-pre-release@^2.0.0-beta.1" + ] + } +} diff --git a/tests/specs/update/pre_release/outdated.out b/tests/specs/update/pre_release/outdated.out new file mode 100644 index 0000000000..b8369b25f7 --- /dev/null +++ b/tests/specs/update/pre_release/outdated.out @@ -0,0 +1,11 @@ +┌────────────────────────────────────┬──────────────┬──────────────┬──────────────┐ +│ Package │ Current │ Update │ Latest │ +├────────────────────────────────────┼──────────────┼──────────────┼──────────────┤ +│ jsr:@denotest/has-only-pre-release │ 2.0.0-beta.1 │ 2.0.0-beta.2 │ 2.0.0-beta.2 │ +├────────────────────────────────────┼──────────────┼──────────────┼──────────────┤ +│ jsr:@denotest/has-pre-release │ 2.0.0-beta.1 │ 2.0.0-beta.2 │ 2.0.0-beta.2 │ +├────────────────────────────────────┼──────────────┼──────────────┼──────────────┤ +│ npm:@denotest/has-pre-release │ 2.0.0-beta.1 │ 2.0.0-beta.2 │ 2.0.0-beta.2 │ +└────────────────────────────────────┴──────────────┴──────────────┴──────────────┘ + +[WILDCARD] diff --git a/tests/specs/update/pre_release/update.out b/tests/specs/update/pre_release/update.out new file mode 100644 index 0000000000..d019457f0a --- /dev/null +++ b/tests/specs/update/pre_release/update.out @@ -0,0 +1,5 @@ +[WILDCARD] +Updated 3 dependencies: + - jsr:@denotest/has-only-pre-release 2.0.0-beta.1 -> 2.0.0-beta.2 + - jsr:@denotest/has-pre-release 2.0.0-beta.1 -> 2.0.0-beta.2 + - npm:@denotest/has-pre-release 2.0.0-beta.1 -> 2.0.0-beta.2 diff --git a/tests/unit_node/_fs/_fs_access_test.ts b/tests/unit_node/_fs/_fs_access_test.ts index f8010b0b8b..0881769f2c 100644 --- a/tests/unit_node/_fs/_fs_access_test.ts +++ b/tests/unit_node/_fs/_fs_access_test.ts @@ -28,6 +28,8 @@ Deno.test( try { await fs.promises.access(file, fs.constants.R_OK); await fs.promises.access(file, fs.constants.W_OK); + await fs.promises.access(file, fs.constants.X_OK); + await fs.promises.access(file, fs.constants.F_OK); } finally { await Deno.remove(file); } @@ -60,6 +62,8 @@ Deno.test( try { fs.accessSync(file, fs.constants.R_OK); fs.accessSync(file, fs.constants.W_OK); + fs.accessSync(file, fs.constants.X_OK); + fs.accessSync(file, fs.constants.F_OK); } finally { Deno.removeSync(file); } diff --git a/tests/util/server/src/npm.rs b/tests/util/server/src/npm.rs index 081989ddb5..0261b2532c 100644 --- a/tests/util/server/src/npm.rs +++ b/tests/util/server/src/npm.rs @@ -267,6 +267,7 @@ fn get_npm_package( let mut tarballs = HashMap::new(); let mut versions = serde_json::Map::new(); let mut latest_version = semver::Version::parse("0.0.0").unwrap(); + let mut dist_tags = serde_json::Map::new(); for entry in fs::read_dir(&package_folder)? { let entry = entry?; let file_type = entry.file_type()?; @@ -345,6 +346,14 @@ fn get_npm_package( } } + if let Some(publish_config) = version_info.get("publishConfig") { + if let Some(tag) = publish_config.get("tag") { + if let Some(tag) = tag.as_str() { + dist_tags.insert(tag.to_string(), version.clone().into()); + } + } + } + versions.insert(version.clone(), version_info.into()); let version = semver::Version::parse(&version)?; if version.cmp(&latest_version).is_gt() { @@ -352,8 +361,9 @@ fn get_npm_package( } } - let mut dist_tags = serde_json::Map::new(); - dist_tags.insert("latest".to_string(), latest_version.to_string().into()); + if !dist_tags.contains_key("latest") { + dist_tags.insert("latest".to_string(), latest_version.to_string().into()); + } // create the registry file for this package let mut registry_file = serde_json::Map::new();