diff --git a/Cargo.lock b/Cargo.lock index 14ee88169d..39b0c00e61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -705,6 +705,7 @@ dependencies = [ "once_cell", "os_pipe", "pretty_assertions", + "regex", "serde", "test_server", "tokio", diff --git a/cli/args/flags.rs b/cli/args/flags.rs index dca9cfa492..9de1c6057f 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -26,7 +26,6 @@ use serde::Serialize; use std::env; use std::ffi::OsString; use std::net::SocketAddr; -use std::num::NonZeroU16; use std::num::NonZeroU32; use std::num::NonZeroU8; use std::num::NonZeroUsize; @@ -283,7 +282,7 @@ impl RunFlags { pub struct ServeFlags { pub script: String, pub watch: Option, - pub port: NonZeroU16, + pub port: u16, pub host: String, } @@ -293,7 +292,7 @@ impl ServeFlags { Self { script, watch: None, - port: NonZeroU16::new(port).unwrap(), + port, host: host.to_owned(), } } @@ -2464,8 +2463,8 @@ fn serve_subcommand() -> Command { .arg( Arg::new("port") .long("port") - .help("The TCP port to serve on, defaulting to 8000.") - .value_parser(value_parser!(NonZeroU16)), + .help("The TCP port to serve on, defaulting to 8000. Pass 0 to pick a random free port.") + .value_parser(value_parser!(u16)), ) .arg( Arg::new("host") @@ -4127,9 +4126,7 @@ fn serve_parse( app: Command, ) -> clap::error::Result<()> { // deno serve implies --allow-net=host:port - let port = matches - .remove_one::("port") - .unwrap_or(NonZeroU16::new(8000).unwrap()); + let port = matches.remove_one::("port").unwrap_or(8000); let host = matches .remove_one::("host") .unwrap_or_else(|| "0.0.0.0".to_owned()); @@ -5322,6 +5319,32 @@ mod tests { ..Flags::default() } ); + + let r = flags_from_vec(svec![ + "deno", + "serve", + "--port", + "0", + "--host", + "example.com", + "main.ts" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Serve(ServeFlags::new_default( + "main.ts".to_string(), + 0, + "example.com" + )), + permissions: PermissionFlags { + allow_net: Some(vec!["example.com:0".to_owned()]), + ..Default::default() + }, + code_cache_enabled: true, + ..Flags::default() + } + ); } #[test] diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 0a0f7d7043..b3d508e18a 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -62,7 +62,6 @@ use std::env; use std::io::BufReader; use std::io::Cursor; use std::net::SocketAddr; -use std::num::NonZeroU16; use std::num::NonZeroUsize; use std::path::Path; use std::path::PathBuf; @@ -1036,7 +1035,7 @@ impl CliOptions { } } - pub fn serve_port(&self) -> Option { + pub fn serve_port(&self) -> Option { if let DenoSubcommand::Serve(flags) = self.sub_command() { Some(flags.port) } else { diff --git a/cli/worker.rs b/cli/worker.rs index 151a4ec4f9..bfdeb35689 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use std::num::NonZeroU16; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; @@ -148,7 +147,7 @@ struct SharedWorkerState { disable_deprecated_api_warning: bool, verbose_deprecated_api_warning: bool, code_cache: Option>, - serve_port: Option, + serve_port: Option, serve_host: Option, } @@ -418,7 +417,7 @@ impl CliMainWorkerFactory { feature_checker: Arc, options: CliMainWorkerOptions, node_ipc: Option, - serve_port: Option, + serve_port: Option, serve_host: Option, enable_future_features: bool, disable_deprecated_api_warning: bool, diff --git a/runtime/worker_bootstrap.rs b/runtime/worker_bootstrap.rs index e1abf87fce..0838da2d1a 100644 --- a/runtime/worker_bootstrap.rs +++ b/runtime/worker_bootstrap.rs @@ -4,7 +4,6 @@ use deno_core::v8; use deno_core::ModuleSpecifier; use serde::Serialize; use std::cell::RefCell; -use std::num::NonZeroU16; use std::thread; use deno_terminal::colors; @@ -93,7 +92,7 @@ pub struct BootstrapOptions { pub future: bool, pub mode: WorkerExecutionMode, // Used by `deno serve` - pub serve_port: Option, + pub serve_port: Option, pub serve_host: Option, } @@ -198,7 +197,7 @@ impl BootstrapOptions { self.verbose_deprecated_api_warning, self.future, self.mode as u8 as _, - self.serve_port.map(|x| x.into()).unwrap_or_default(), + self.serve_port.unwrap_or_default(), self.serve_host.as_deref(), ); diff --git a/tests/Cargo.toml b/tests/Cargo.toml index fa633f6070..af220aae7a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -52,6 +52,7 @@ hyper-util.workspace = true once_cell.workspace = true os_pipe.workspace = true pretty_assertions.workspace = true +regex.workspace = true serde.workspace = true test_util.workspace = true tokio.workspace = true diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index 59bf0db372..d35fabc027 100644 --- a/tests/integration/mod.rs +++ b/tests/integration/mod.rs @@ -60,6 +60,8 @@ mod publish; mod repl; #[path = "run_tests.rs"] mod run; +#[path = "serve_tests.rs"] +mod serve; #[path = "shared_library_tests.rs"] mod shared_library_tests; #[path = "task_tests.rs"] diff --git a/tests/integration/serve_tests.rs b/tests/integration/serve_tests.rs new file mode 100644 index 0000000000..92b0576ed1 --- /dev/null +++ b/tests/integration/serve_tests.rs @@ -0,0 +1,51 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::io::Read; + +use deno_fetch::reqwest; +use pretty_assertions::assert_eq; +use regex::Regex; +use test_util as util; + +#[tokio::test] +async fn deno_serve_port_0() { + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("serve") + .arg("--port") + .arg("0") + .arg("./serve.ts") + .stdout_piped() + .spawn() + .unwrap(); + let stdout = child.stdout.as_mut().unwrap(); + let mut buffer = [0; 50]; + let _read = stdout.read(&mut buffer).unwrap(); + let msg = std::str::from_utf8(&buffer).unwrap(); + let port_regex = Regex::new(r"(\d+)").unwrap(); + let port = port_regex.find(msg).unwrap().as_str(); + + let cert = reqwest::Certificate::from_pem(include_bytes!( + "../testdata/tls/RootCA.crt" + )) + .unwrap(); + + let client = reqwest::Client::builder() + .add_root_certificate(cert) + .http2_prior_knowledge() + .build() + .unwrap(); + + let res = client + .get(&format!("http://127.0.0.1:{port}")) + .send() + .await + .unwrap(); + assert_eq!(200, res.status()); + + let body = res.text().await.unwrap(); + assert_eq!(body, "deno serve --port 0 works!"); + + child.kill().unwrap(); + child.wait().unwrap(); +} diff --git a/tests/testdata/serve.ts b/tests/testdata/serve.ts new file mode 100644 index 0000000000..cdd8476eba --- /dev/null +++ b/tests/testdata/serve.ts @@ -0,0 +1,5 @@ +export default { + fetch(_req: Request) { + return new Response("deno serve --port 0 works!"); + }, +};