summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/args/flags.rs247
-rw-r--r--cli/args/mod.rs21
-rw-r--r--cli/factory.rs3
-rw-r--r--cli/main.rs6
-rw-r--r--cli/standalone/mod.rs9
-rw-r--r--cli/tools/bench/mod.rs2
-rw-r--r--cli/tools/jupyter/mod.rs2
-rw-r--r--cli/tools/repl/mod.rs2
-rw-r--r--cli/tools/run/mod.rs18
-rw-r--r--cli/tools/test/mod.rs2
-rw-r--r--cli/worker.rs22
-rw-r--r--ext/http/00_serve.ts29
-rw-r--r--runtime/js/99_main.js58
-rw-r--r--runtime/lib.rs1
-rw-r--r--runtime/worker_bootstrap.rs42
-rw-r--r--tests/specs/serve/bad/__test__.jsonc5
-rw-r--r--tests/specs/serve/bad/main.out1
-rw-r--r--tests/specs/serve/bad/main.ts4
-rw-r--r--tests/specs/serve/basic/__test__.jsonc5
-rw-r--r--tests/specs/serve/basic/main.out1
-rw-r--r--tests/specs/serve/basic/main.ts18
21 files changed, 484 insertions, 14 deletions
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index d57f78aff..71398d355 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -22,6 +22,7 @@ use log::Level;
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;
@@ -272,6 +273,26 @@ impl RunFlags {
}
}
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct ServeFlags {
+ pub script: String,
+ pub watch: Option<WatchFlagsWithPaths>,
+ pub port: NonZeroU16,
+ pub host: String,
+}
+
+impl ServeFlags {
+ #[cfg(test)]
+ pub fn new_default(script: String, port: u16, host: &str) -> Self {
+ Self {
+ script,
+ watch: None,
+ port: NonZeroU16::new(port).unwrap(),
+ host: host.to_owned(),
+ }
+ }
+}
+
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct WatchFlags {
pub hmr: bool,
@@ -366,6 +387,7 @@ pub enum DenoSubcommand {
Lint(LintFlags),
Repl(ReplFlags),
Run(RunFlags),
+ Serve(ServeFlags),
Task(TaskFlags),
Test(TestFlags),
Types,
@@ -776,7 +798,7 @@ impl Flags {
use DenoSubcommand::*;
match &self.subcommand {
- Run(RunFlags { script, .. }) => {
+ Run(RunFlags { script, .. }) | Serve(ServeFlags { script, .. }) => {
let module_specifier = resolve_url_or_path(script, current_dir).ok()?;
if module_specifier.scheme() == "file" {
let p = module_specifier
@@ -1063,6 +1085,7 @@ pub fn flags_from_vec(args: Vec<OsString>) -> clap::error::Result<Flags> {
"lsp" => lsp_parse(&mut flags, &mut m),
"repl" => repl_parse(&mut flags, &mut m),
"run" => run_parse(&mut flags, &mut m, app)?,
+ "serve" => serve_parse(&mut flags, &mut m, app)?,
"task" => task_parse(&mut flags, &mut m),
"test" => test_parse(&mut flags, &mut m),
"types" => types_parse(&mut flags, &mut m),
@@ -1198,6 +1221,7 @@ fn clap_root() -> Command {
.global(true),
)
.subcommand(run_subcommand())
+ .subcommand(serve_subcommand())
.defer(|cmd| {
cmd
.subcommand(add_subcommand())
@@ -2265,6 +2289,59 @@ Specifying the filename '-' to read the file from stdin.
)
}
+fn serve_host_validator(host: &str) -> Result<String, String> {
+ if Url::parse(&format!("internal://{host}:9999")).is_ok() {
+ Ok(host.to_owned())
+ } else {
+ Err(format!("Bad serve host: {host}"))
+ }
+}
+
+fn serve_subcommand() -> Command {
+ runtime_args(Command::new("serve"), true, true)
+ .arg(
+ Arg::new("port")
+ .long("port")
+ .help("The TCP port to serve on, defaulting to 8000.")
+ .value_parser(value_parser!(NonZeroU16)),
+ )
+ .arg(
+ Arg::new("host")
+ .long("host")
+ .help("The TCP address to serve on, defaulting to 0.0.0.0 (all interfaces).")
+ .value_parser(serve_host_validator),
+ )
+ .arg(check_arg(false))
+ .arg(watch_arg(true))
+ .arg(watch_exclude_arg())
+ .arg(hmr_arg(true))
+ .arg(no_clear_screen_arg())
+ .arg(executable_ext_arg())
+ .arg(
+ script_arg()
+ .required_unless_present("v8-flags")
+ .trailing_var_arg(true),
+ )
+ .arg(env_file_arg())
+ .arg(no_code_cache_arg())
+ .about("Run a server")
+ .long_about("Run a server defined in a main module
+
+The serve command uses the default exports of the main module to determine which
+servers to start.
+
+See https://docs.deno.com/runtime/manual/tools/serve for
+more detailed information.
+
+Start a server defined in server.ts:
+
+ deno serve server.ts
+
+Start a server defined in server.ts, watching for changes and running on port 5050:
+
+ deno serve --watch --port 5050 server.ts")
+}
+
fn task_subcommand() -> Command {
Command::new("task")
.about("Run a task defined in the configuration file")
@@ -3863,6 +3940,63 @@ fn run_parse(
Ok(())
}
+fn serve_parse(
+ flags: &mut Flags,
+ matches: &mut ArgMatches,
+ app: Command,
+) -> clap::error::Result<()> {
+ // deno serve implies --allow-net=host:port
+ let port = matches
+ .remove_one::<NonZeroU16>("port")
+ .unwrap_or(NonZeroU16::new(8000).unwrap());
+ let host = matches
+ .remove_one::<String>("host")
+ .unwrap_or_else(|| "0.0.0.0".to_owned());
+
+ runtime_args_parse(flags, matches, true, true);
+ // If the user didn't pass --allow-net, add this port to the network
+ // allowlist. If the host is 0.0.0.0, we add :{port} and allow the same network perms
+ // as if it was passed to --allow-net directly.
+ let allowed = flags_net::parse(vec![if host == "0.0.0.0" {
+ format!(":{port}")
+ } else {
+ format!("{host}:{port}")
+ }])?;
+ match &mut flags.allow_net {
+ None => flags.allow_net = Some(allowed),
+ Some(v) => {
+ if !v.is_empty() {
+ v.extend(allowed);
+ }
+ }
+ }
+ flags.code_cache_enabled = !matches.get_flag("no-code-cache");
+
+ let mut script_arg =
+ matches.remove_many::<String>("script_arg").ok_or_else(|| {
+ let mut app = app;
+ let subcommand = &mut app.find_subcommand_mut("serve").unwrap();
+ subcommand.error(
+ clap::error::ErrorKind::MissingRequiredArgument,
+ "[SCRIPT_ARG] may only be omitted with --v8-flags=--help",
+ )
+ })?;
+
+ let script = script_arg.next().unwrap();
+ flags.argv.extend(script_arg);
+
+ ext_arg_parse(flags, matches);
+
+ flags.subcommand = DenoSubcommand::Serve(ServeFlags {
+ script,
+ watch: watch_arg_parse_with_paths(matches),
+ port,
+ host,
+ });
+
+ Ok(())
+}
+
fn task_parse(flags: &mut Flags, matches: &mut ArgMatches) {
flags.config_flag = matches
.remove_one::<String>("config")
@@ -4858,7 +4992,7 @@ mod tests {
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Run(RunFlags::new_default(
- "script.ts".to_string()
+ "script.ts".to_string(),
)),
v8_flags: svec!["--expose-gc", "--gc-stats=1"],
code_cache_enabled: true,
@@ -4874,6 +5008,115 @@ mod tests {
}
#[test]
+ fn serve_flags() {
+ let r = flags_from_vec(svec!["deno", "serve", "main.ts"]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
+ "main.ts".to_string(),
+ 8000,
+ "0.0.0.0"
+ )),
+ allow_net: Some(vec![
+ "0.0.0.0:8000".to_string(),
+ "127.0.0.1:8000".to_string(),
+ "localhost:8000".to_string()
+ ]),
+ code_cache_enabled: true,
+ ..Flags::default()
+ }
+ );
+ let r = flags_from_vec(svec!["deno", "serve", "--port", "5000", "main.ts"]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
+ "main.ts".to_string(),
+ 5000,
+ "0.0.0.0"
+ )),
+ allow_net: Some(vec![
+ "0.0.0.0:5000".to_string(),
+ "127.0.0.1:5000".to_string(),
+ "localhost:5000".to_string()
+ ]),
+ code_cache_enabled: true,
+ ..Flags::default()
+ }
+ );
+ let r = flags_from_vec(svec![
+ "deno",
+ "serve",
+ "--port",
+ "5000",
+ "--allow-net=example.com",
+ "main.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
+ "main.ts".to_string(),
+ 5000,
+ "0.0.0.0"
+ )),
+ allow_net: Some(vec![
+ "example.com".to_string(),
+ "0.0.0.0:5000".to_string(),
+ "127.0.0.1:5000".to_string(),
+ "localhost:5000".to_string()
+ ]),
+ code_cache_enabled: true,
+ ..Flags::default()
+ }
+ );
+ let r = flags_from_vec(svec![
+ "deno",
+ "serve",
+ "--port",
+ "5000",
+ "--allow-net",
+ "main.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
+ "main.ts".to_string(),
+ 5000,
+ "0.0.0.0"
+ )),
+ allow_net: Some(vec![]),
+ code_cache_enabled: true,
+ ..Flags::default()
+ }
+ );
+ let r = flags_from_vec(svec![
+ "deno",
+ "serve",
+ "--port",
+ "5000",
+ "--host",
+ "example.com",
+ "main.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Serve(ServeFlags::new_default(
+ "main.ts".to_string(),
+ 5000,
+ "example.com"
+ )),
+ allow_net: Some(vec!["example.com:5000".to_owned()]),
+ code_cache_enabled: true,
+ ..Flags::default()
+ }
+ );
+ }
+
+ #[test]
fn has_permission() {
let r = flags_from_vec(svec!["deno", "run", "--allow-read", "x.ts"]);
assert_eq!(r.unwrap().has_permission(), true);
diff --git a/cli/args/mod.rs b/cli/args/mod.rs
index aa3622d09..bb0ef1aba 100644
--- a/cli/args/mod.rs
+++ b/cli/args/mod.rs
@@ -61,6 +61,7 @@ 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;
@@ -1022,6 +1023,22 @@ impl CliOptions {
}
}
+ pub fn serve_port(&self) -> Option<NonZeroU16> {
+ if let DenoSubcommand::Serve(flags) = self.sub_command() {
+ Some(flags.port)
+ } else {
+ None
+ }
+ }
+
+ pub fn serve_host(&self) -> Option<String> {
+ if let DenoSubcommand::Serve(flags) = self.sub_command() {
+ Some(flags.host.clone())
+ } else {
+ None
+ }
+ }
+
pub fn enable_future_features(&self) -> bool {
*DENO_FUTURE
}
@@ -1062,6 +1079,10 @@ impl CliOptions {
.map_err(AnyError::from)
}
}
+ DenoSubcommand::Serve(run_flags) => {
+ resolve_url_or_path(&run_flags.script, self.initial_cwd())
+ .map_err(AnyError::from)
+ }
_ => {
bail!("No main module.")
}
diff --git a/cli/factory.rs b/cli/factory.rs
index 1a0584eea..bfaf96f39 100644
--- a/cli/factory.rs
+++ b/cli/factory.rs
@@ -219,6 +219,7 @@ impl CliFactory {
// Warm up the caches we know we'll likely need based on the CLI mode
match self.options.sub_command() {
DenoSubcommand::Run(_)
+ | DenoSubcommand::Serve(_)
| DenoSubcommand::Bench(_)
| DenoSubcommand::Test(_)
| DenoSubcommand::Check(_) => {
@@ -816,6 +817,8 @@ impl CliFactory {
self.feature_checker().clone(),
self.create_cli_main_worker_options()?,
self.options.node_ipc_fd(),
+ self.options.serve_port(),
+ self.options.serve_host(),
self.options.enable_future_features(),
// TODO(bartlomieju): temporarily disabled
// self.options.disable_deprecated_api_warning,
diff --git a/cli/main.rs b/cli/main.rs
index a4e93ca31..142ae017c 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -33,6 +33,7 @@ use crate::util::display;
use crate::util::v8::get_v8_flags_from_env;
use crate::util::v8::init_v8_flags;
+use deno_runtime::WorkerExecutionMode;
pub use deno_runtime::UNSTABLE_GRANULAR_FLAGS;
use deno_core::anyhow::Context;
@@ -174,9 +175,12 @@ async fn run_subcommand(flags: Flags) -> Result<i32, AnyError> {
if run_flags.is_stdin() {
tools::run::run_from_stdin(flags).await
} else {
- tools::run::run_script(flags, run_flags).await
+ tools::run::run_script(WorkerExecutionMode::Run, flags, run_flags.watch).await
}
}),
+ DenoSubcommand::Serve(serve_flags) => spawn_subcommand(async move {
+ tools::run::run_script(WorkerExecutionMode::Serve, flags, serve_flags.watch).await
+ }),
DenoSubcommand::Task(task_flags) => spawn_subcommand(async {
tools::task::execute_script(flags, task_flags).await
}),
diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs
index 003e2bf79..8ff822f5a 100644
--- a/cli/standalone/mod.rs
+++ b/cli/standalone/mod.rs
@@ -50,6 +50,7 @@ use deno_runtime::deno_tls::rustls::RootCertStore;
use deno_runtime::deno_tls::RootCertStoreProvider;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
+use deno_runtime::WorkerExecutionMode;
use deno_runtime::WorkerLogLevel;
use deno_semver::npm::NpmPackageReqReference;
use import_map::parse_from_json;
@@ -567,6 +568,8 @@ pub async fn run(
create_coverage_collector: None,
},
None,
+ None,
+ None,
false,
// TODO(bartlomieju): temporarily disabled
// metadata.disable_deprecated_api_warning,
@@ -581,7 +584,11 @@ pub async fn run(
deno_core::JsRuntime::init_platform(None);
let mut worker = worker_factory
- .create_main_worker(main_module.clone(), permissions)
+ .create_main_worker(
+ WorkerExecutionMode::Run,
+ main_module.clone(),
+ permissions,
+ )
.await?;
let exit_code = worker.run().await?;
diff --git a/cli/tools/bench/mod.rs b/cli/tools/bench/mod.rs
index 95986f320..e2411ed12 100644
--- a/cli/tools/bench/mod.rs
+++ b/cli/tools/bench/mod.rs
@@ -35,6 +35,7 @@ use deno_core::PollEventLoopOptions;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
use deno_runtime::tokio_util::create_and_run_current_thread;
+use deno_runtime::WorkerExecutionMode;
use indexmap::IndexMap;
use indexmap::IndexSet;
use log::Level;
@@ -204,6 +205,7 @@ async fn bench_specifier_inner(
) -> Result<(), AnyError> {
let mut worker = worker_factory
.create_custom_worker(
+ WorkerExecutionMode::Bench,
specifier.clone(),
PermissionsContainer::new(permissions),
vec![ops::bench::deno_bench::init_ops(sender.clone())],
diff --git a/cli/tools/jupyter/mod.rs b/cli/tools/jupyter/mod.rs
index 64c8cd7df..6531b0339 100644
--- a/cli/tools/jupyter/mod.rs
+++ b/cli/tools/jupyter/mod.rs
@@ -22,6 +22,7 @@ use deno_runtime::deno_io::Stdio;
use deno_runtime::deno_io::StdioPipe;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
+use deno_runtime::WorkerExecutionMode;
use deno_terminal::colors;
use tokio::sync::mpsc;
use tokio::sync::mpsc::UnboundedSender;
@@ -88,6 +89,7 @@ pub async fn kernel(
let mut worker = worker_factory
.create_custom_worker(
+ WorkerExecutionMode::Jupyter,
main_module.clone(),
permissions,
vec![
diff --git a/cli/tools/repl/mod.rs b/cli/tools/repl/mod.rs
index 03b8e512e..d1c1cab71 100644
--- a/cli/tools/repl/mod.rs
+++ b/cli/tools/repl/mod.rs
@@ -15,6 +15,7 @@ use deno_core::serde_json;
use deno_core::unsync::spawn_blocking;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
+use deno_runtime::WorkerExecutionMode;
use rustyline::error::ReadlineError;
mod channel;
@@ -170,6 +171,7 @@ pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result<i32, AnyError> {
let test_event_sender = worker.sender;
let mut worker = worker_factory
.create_custom_worker(
+ WorkerExecutionMode::Repl,
main_module.clone(),
permissions,
vec![crate::ops::testing::deno_test::init_ops(test_event_sender)],
diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs
index 793b55a8a..9f4bfeb96 100644
--- a/cli/tools/run/mod.rs
+++ b/cli/tools/run/mod.rs
@@ -5,10 +5,10 @@ use std::io::Read;
use deno_core::error::AnyError;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
+use deno_runtime::WorkerExecutionMode;
use crate::args::EvalFlags;
use crate::args::Flags;
-use crate::args::RunFlags;
use crate::args::WatchFlagsWithPaths;
use crate::factory::CliFactory;
use crate::factory::CliFactoryBuilder;
@@ -19,8 +19,9 @@ use crate::util::file_watcher::WatcherRestartMode;
pub mod hmr;
pub async fn run_script(
+ mode: WorkerExecutionMode,
flags: Flags,
- run_flags: RunFlags,
+ watch: Option<WatchFlagsWithPaths>,
) -> Result<i32, AnyError> {
if !flags.has_permission() && flags.has_permission_in_argv() {
log::warn!(
@@ -33,8 +34,8 @@ To grant permissions, set them before the script argument. For example:
);
}
- if let Some(watch_flags) = run_flags.watch {
- return run_with_watch(flags, watch_flags).await;
+ if let Some(watch_flags) = watch {
+ return run_with_watch(mode, flags, watch_flags).await;
}
// TODO(bartlomieju): actually I think it will also fail if there's an import
@@ -68,7 +69,7 @@ To grant permissions, set them before the script argument. For example:
)?);
let worker_factory = factory.create_cli_main_worker_factory().await?;
let mut worker = worker_factory
- .create_main_worker(main_module, permissions)
+ .create_main_worker(mode, main_module, permissions)
.await?;
let exit_code = worker.run().await?;
@@ -98,7 +99,7 @@ pub async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
});
let mut worker = worker_factory
- .create_main_worker(main_module, permissions)
+ .create_main_worker(WorkerExecutionMode::Run, main_module, permissions)
.await?;
let exit_code = worker.run().await?;
Ok(exit_code)
@@ -107,6 +108,7 @@ pub async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
// TODO(bartlomieju): this function is not handling `exit_code` set by the runtime
// code properly.
async fn run_with_watch(
+ mode: WorkerExecutionMode,
flags: Flags,
watch_flags: WatchFlagsWithPaths,
) -> Result<i32, AnyError> {
@@ -135,7 +137,7 @@ async fn run_with_watch(
let mut worker = factory
.create_cli_main_worker_factory()
.await?
- .create_main_worker(main_module, permissions)
+ .create_main_worker(mode, main_module, permissions)
.await?;
if watch_flags.hmr {
@@ -184,7 +186,7 @@ pub async fn eval_command(
)?);
let worker_factory = factory.create_cli_main_worker_factory().await?;
let mut worker = worker_factory
- .create_main_worker(main_module, permissions)
+ .create_main_worker(WorkerExecutionMode::Eval, main_module, permissions)
.await?;
let exit_code = worker.run().await?;
Ok(exit_code)
diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs
index 013d8c084..ffa0fef9e 100644
--- a/cli/tools/test/mod.rs
+++ b/cli/tools/test/mod.rs
@@ -59,6 +59,7 @@ use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
use deno_runtime::tokio_util::create_and_run_current_thread;
use deno_runtime::worker::MainWorker;
+use deno_runtime::WorkerExecutionMode;
use indexmap::IndexMap;
use indexmap::IndexSet;
use log::Level;
@@ -583,6 +584,7 @@ async fn configure_main_worker(
) -> Result<(Option<Box<dyn CoverageCollector>>, MainWorker), anyhow::Error> {
let mut worker = worker_factory
.create_custom_worker(
+ WorkerExecutionMode::Test,
specifier.clone(),
PermissionsContainer::new(permissions),
vec![ops::testing::deno_test::init_ops(worker_sender.sender)],
diff --git a/cli/worker.rs b/cli/worker.rs
index 7dbb9b177..edc4ef907 100644
--- a/cli/worker.rs
+++ b/cli/worker.rs
@@ -1,5 +1,6 @@
// 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;
@@ -40,6 +41,7 @@ use deno_runtime::web_worker::WebWorkerOptions;
use deno_runtime::worker::MainWorker;
use deno_runtime::worker::WorkerOptions;
use deno_runtime::BootstrapOptions;
+use deno_runtime::WorkerExecutionMode;
use deno_runtime::WorkerLogLevel;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReqReference;
@@ -142,6 +144,8 @@ struct SharedWorkerState {
disable_deprecated_api_warning: bool,
verbose_deprecated_api_warning: bool,
code_cache: Option<Arc<dyn code_cache::CodeCache>>,
+ serve_port: Option<NonZeroU16>,
+ serve_host: Option<String>,
}
impl SharedWorkerState {
@@ -410,6 +414,8 @@ impl CliMainWorkerFactory {
feature_checker: Arc<FeatureChecker>,
options: CliMainWorkerOptions,
node_ipc: Option<i64>,
+ serve_port: Option<NonZeroU16>,
+ serve_host: Option<String>,
enable_future_features: bool,
disable_deprecated_api_warning: bool,
verbose_deprecated_api_warning: bool,
@@ -434,6 +440,8 @@ impl CliMainWorkerFactory {
maybe_lockfile,
feature_checker,
node_ipc,
+ serve_port,
+ serve_host,
enable_future_features,
disable_deprecated_api_warning,
verbose_deprecated_api_warning,
@@ -444,11 +452,13 @@ impl CliMainWorkerFactory {
pub async fn create_main_worker(
&self,
+ mode: WorkerExecutionMode,
main_module: ModuleSpecifier,
permissions: PermissionsContainer,
) -> Result<CliMainWorker, AnyError> {
self
.create_custom_worker(
+ mode,
main_module,
permissions,
vec![],
@@ -459,6 +469,7 @@ impl CliMainWorkerFactory {
pub async fn create_custom_worker(
&self,
+ mode: WorkerExecutionMode,
main_module: ModuleSpecifier,
permissions: PermissionsContainer,
custom_extensions: Vec<Extension>,
@@ -545,7 +556,7 @@ impl CliMainWorkerFactory {
let maybe_inspector_server = shared.maybe_inspector_server.clone();
let create_web_worker_cb =
- create_web_worker_callback(shared.clone(), stdio.clone());
+ create_web_worker_callback(mode, shared.clone(), stdio.clone());
let maybe_storage_key = shared
.storage_key_resolver
@@ -600,6 +611,9 @@ impl CliMainWorkerFactory {
disable_deprecated_api_warning: shared.disable_deprecated_api_warning,
verbose_deprecated_api_warning: shared.verbose_deprecated_api_warning,
future: shared.enable_future_features,
+ mode,
+ serve_port: shared.serve_port,
+ serve_host: shared.serve_host.clone(),
},
extensions: custom_extensions,
startup_snapshot: crate::js::deno_isolate_init(),
@@ -745,6 +759,7 @@ impl CliMainWorkerFactory {
}
fn create_web_worker_callback(
+ mode: WorkerExecutionMode,
shared: Arc<SharedWorkerState>,
stdio: deno_runtime::deno_io::Stdio,
) -> Arc<CreateWebWorkerCb> {
@@ -758,7 +773,7 @@ fn create_web_worker_callback(
let maybe_source_map_getter =
shared.module_loader_factory.create_source_map_getter();
let create_web_worker_cb =
- create_web_worker_callback(shared.clone(), stdio.clone());
+ create_web_worker_callback(mode, shared.clone(), stdio.clone());
let maybe_storage_key = shared
.storage_key_resolver
@@ -805,6 +820,9 @@ fn create_web_worker_callback(
disable_deprecated_api_warning: shared.disable_deprecated_api_warning,
verbose_deprecated_api_warning: shared.verbose_deprecated_api_warning,
future: shared.enable_future_features,
+ mode,
+ serve_port: shared.serve_port,
+ serve_host: shared.serve_host.clone(),
},
extensions: vec![],
startup_snapshot: crate::js::deno_isolate_init(),
diff --git a/ext/http/00_serve.ts b/ext/http/00_serve.ts
index 1063f9691..d19b62dde 100644
--- a/ext/http/00_serve.ts
+++ b/ext/http/00_serve.ts
@@ -791,8 +791,37 @@ internals.upgradeHttpRaw = upgradeHttpRaw;
internals.serveHttpOnListener = serveHttpOnListener;
internals.serveHttpOnConnection = serveHttpOnConnection;
+function registerDeclarativeServer(exports) {
+ if (ObjectHasOwn(exports, "fetch")) {
+ if (typeof exports.fetch !== "function" || exports.fetch.length !== 1) {
+ throw new TypeError(
+ "Invalid type for fetch: must be a function with a single parameter",
+ );
+ }
+ return ({ servePort, serveHost }) => {
+ Deno.serve({
+ port: servePort,
+ hostname: serveHost,
+ onListen: ({ port, hostname }) => {
+ console.debug(
+ `%cdeno serve%c: Listening on %chttp://${hostname}:${port}/%c`,
+ "color: green",
+ "color: inherit",
+ "color: yellow",
+ "color: inherit",
+ );
+ },
+ handler: (req) => {
+ return exports.fetch(req);
+ },
+ });
+ };
+ }
+}
+
export {
addTrailers,
+ registerDeclarativeServer,
serve,
serveHttpOnConnection,
serveHttpOnListener,
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 8164e0c9a..59d2a1434 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -35,6 +35,7 @@ const {
ObjectAssign,
ObjectDefineProperties,
ObjectDefineProperty,
+ ObjectHasOwn,
ObjectKeys,
ObjectPrototypeIsPrototypeOf,
ObjectSetPrototypeOf,
@@ -52,6 +53,7 @@ const {
const {
isNativeError,
} = core;
+import { registerDeclarativeServer } from "ext:deno_http/00_serve.ts";
import * as event from "ext:deno_web/02_event.js";
import * as location from "ext:deno_web/12_location.js";
import * as version from "ext:runtime/01_version.ts";
@@ -679,6 +681,18 @@ const {
target,
} = op_snapshot_options();
+const executionModes = {
+ none: 0,
+ worker: 1,
+ run: 2,
+ repl: 3,
+ eval: 4,
+ test: 5,
+ bench: 6,
+ serve: 7,
+ jupyter: 8,
+};
+
function bootstrapMainRuntime(runtimeOptions, warmup = false) {
if (!warmup) {
if (hasBootstrapped) {
@@ -695,8 +709,52 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
7: shouldDisableDeprecatedApiWarning,
8: shouldUseVerboseDeprecatedApiWarning,
9: future,
+ 10: mode,
+ 11: servePort,
+ 12: serveHost,
} = runtimeOptions;
+ if (mode === executionModes.run || mode === executionModes.serve) {
+ let serve = undefined;
+ core.addMainModuleHandler((main) => {
+ if (ObjectHasOwn(main, "default")) {
+ try {
+ serve = registerDeclarativeServer(main.default);
+ } catch (e) {
+ if (mode === executionModes.serve) {
+ throw e;
+ }
+ }
+ }
+
+ if (mode === executionModes.serve && !serve) {
+ console.error(
+ `%cerror: %cdeno serve requires %cexport default { fetch }%c in the main module, did you mean to run \"deno run\"?`,
+ "color: yellow;",
+ "color: inherit;",
+ "font-weight: bold;",
+ "font-weight: normal;",
+ );
+ return;
+ }
+
+ if (serve) {
+ if (mode === executionModes.run) {
+ console.error(
+ `%cwarning: %cDetected %cexport default { fetch }%c, did you mean to run \"deno serve\"?`,
+ "color: yellow;",
+ "color: inherit;",
+ "font-weight: bold;",
+ "font-weight: normal;",
+ );
+ }
+ if (mode === executionModes.serve) {
+ serve({ servePort, serveHost });
+ }
+ }
+ });
+ }
+
// TODO(iuioiua): remove in Deno v2. This allows us to dynamically delete
// class properties within constructors for classes that are not defined
// within the Deno namespace.
diff --git a/runtime/lib.rs b/runtime/lib.rs
index f33e9b7e3..ec751f207 100644
--- a/runtime/lib.rs
+++ b/runtime/lib.rs
@@ -41,6 +41,7 @@ pub mod worker;
mod worker_bootstrap;
pub use worker_bootstrap::BootstrapOptions;
+pub use worker_bootstrap::WorkerExecutionMode;
pub use worker_bootstrap::WorkerLogLevel;
mod shared;
diff --git a/runtime/worker_bootstrap.rs b/runtime/worker_bootstrap.rs
index c019dae1a..ee3b81c62 100644
--- a/runtime/worker_bootstrap.rs
+++ b/runtime/worker_bootstrap.rs
@@ -4,10 +4,36 @@ 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;
+/// The execution mode for this worker. Some modes may have implicit behaviour.
+#[derive(Copy, Clone)]
+#[repr(u8)]
+pub enum WorkerExecutionMode {
+ /// No special behaviour.
+ None,
+
+ /// Running in a worker.
+ Worker,
+ /// `deno run`
+ Run,
+ /// `deno repl`
+ Repl,
+ /// `deno eval`
+ Eval,
+ /// `deno test`
+ Test,
+ /// `deno bench`
+ Bench,
+ /// `deno serve`
+ Serve,
+ /// `deno jupyter`
+ Jupyter,
+}
+
/// The log level to use when printing diagnostic log messages, warnings,
/// or errors in the worker.
///
@@ -63,6 +89,10 @@ pub struct BootstrapOptions {
pub disable_deprecated_api_warning: bool,
pub verbose_deprecated_api_warning: bool,
pub future: bool,
+ pub mode: WorkerExecutionMode,
+ // Used by `deno serve`
+ pub serve_port: Option<NonZeroU16>,
+ pub serve_host: Option<String>,
}
impl Default for BootstrapOptions {
@@ -94,6 +124,9 @@ impl Default for BootstrapOptions {
disable_deprecated_api_warning: false,
verbose_deprecated_api_warning: false,
future: false,
+ mode: WorkerExecutionMode::None,
+ serve_port: Default::default(),
+ serve_host: Default::default(),
}
}
}
@@ -129,6 +162,12 @@ struct BootstrapV8<'a>(
bool,
// future
bool,
+ // mode
+ i32,
+ // serve port
+ u16,
+ // serve host
+ Option<&'a str>,
);
impl BootstrapOptions {
@@ -151,6 +190,9 @@ impl BootstrapOptions {
self.disable_deprecated_api_warning,
self.verbose_deprecated_api_warning,
self.future,
+ self.mode as u8 as _,
+ self.serve_port.map(|x| x.into()).unwrap_or_default(),
+ self.serve_host.as_deref(),
);
bootstrap.serialize(ser).unwrap()
diff --git a/tests/specs/serve/bad/__test__.jsonc b/tests/specs/serve/bad/__test__.jsonc
new file mode 100644
index 000000000..9a37d60ff
--- /dev/null
+++ b/tests/specs/serve/bad/__test__.jsonc
@@ -0,0 +1,5 @@
+{
+ "args": "serve --port 12345 main.ts",
+ "output": "main.out",
+ "tempDir": true
+}
diff --git a/tests/specs/serve/bad/main.out b/tests/specs/serve/bad/main.out
new file mode 100644
index 000000000..1bb42f909
--- /dev/null
+++ b/tests/specs/serve/bad/main.out
@@ -0,0 +1 @@
+error: deno serve requires export default { fetch } in the main module, did you mean to run "deno run"?
diff --git a/tests/specs/serve/bad/main.ts b/tests/specs/serve/bad/main.ts
new file mode 100644
index 000000000..3399721d4
--- /dev/null
+++ b/tests/specs/serve/bad/main.ts
@@ -0,0 +1,4 @@
+export default {
+ bad() {
+ },
+};
diff --git a/tests/specs/serve/basic/__test__.jsonc b/tests/specs/serve/basic/__test__.jsonc
new file mode 100644
index 000000000..9a37d60ff
--- /dev/null
+++ b/tests/specs/serve/basic/__test__.jsonc
@@ -0,0 +1,5 @@
+{
+ "args": "serve --port 12345 main.ts",
+ "output": "main.out",
+ "tempDir": true
+}
diff --git a/tests/specs/serve/basic/main.out b/tests/specs/serve/basic/main.out
new file mode 100644
index 000000000..e79d76af5
--- /dev/null
+++ b/tests/specs/serve/basic/main.out
@@ -0,0 +1 @@
+deno serve: Listening on http://localhost:12345/
diff --git a/tests/specs/serve/basic/main.ts b/tests/specs/serve/basic/main.ts
new file mode 100644
index 000000000..9e299ab21
--- /dev/null
+++ b/tests/specs/serve/basic/main.ts
@@ -0,0 +1,18 @@
+(async () => {
+ for (let i = 0; i < 1000; i++) {
+ try {
+ const resp = await fetch("http://localhost:12345/");
+ Deno.exit(0);
+ } catch {
+ await new Promise((r) => setTimeout(r, 10));
+ }
+ }
+
+ Deno.exit(2);
+})();
+
+export default {
+ fetch(req) {
+ return new Response("Hello world!");
+ },
+};