mod args;
mod cache;
mod cdp;
mod factory;
mod file_fetcher;
mod graph_container;
mod graph_util;
mod http_util;
mod jsr;
mod lsp;
mod module_loader;
mod node;
mod node_compat_shim;
mod npm;
mod ops;
mod registry;
mod resolver;
mod standalone;
mod task_runner;
mod tools;
mod tsc;
mod type_checker;
mod util;
mod worker;
pub(crate) mod sys {
#[allow(clippy::disallowed_types, reason = "definition")]
pub type CliSys = sys_traits::impls::RealSys;
}
use std::borrow::Cow;
use std::collections::HashMap;
use std::env;
use std::future::Future;
use std::io::IsTerminal;
use std::io::Write as _;
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::Arc;
use args::TaskFlags;
use deno_config::glob::FilePatterns;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::unsync::JoinHandle;
use deno_lib::util::result::any_and_jserrorbox_downcast_ref;
use deno_lib::util::result::js_error_downcast_ref;
use deno_lib::worker::LibWorkerFactoryRoots;
use deno_resolver::npm::ByonmResolvePkgFolderFromDenoReqError;
use deno_resolver::npm::ResolvePkgFolderFromDenoReqError;
use deno_runtime::UnconfiguredRuntime;
use deno_runtime::WorkerExecutionMode;
use deno_runtime::fmt_errors::format_js_error;
use deno_runtime::tokio_util::create_and_run_current_thread_with_maybe_metrics;
use deno_telemetry::OtelConfig;
use deno_terminal::colors;
use factory::CliFactory;
use util::fs::canonicalize_path;
const MODULE_NOT_FOUND: &str = "Module not found";
const UNSUPPORTED_SCHEME: &str = "Unsupported scheme";
const UNSUPPORTED_DIR_IMPORT: &str =
"[ERR_UNSUPPORTED_DIR_IMPORT] Directory import";
use self::util::draw_thread::DrawThread;
use self::util::env::resolve_cwd;
use crate::args::CompletionsFlags;
use crate::args::DenoSubcommand;
use crate::args::Flags;
use crate::args::FlagsExt;
use crate::args::flags_from_vec_with_initial_cwd;
use crate::args::get_default_v8_flags;
use crate::util::display;
use crate::util::env::WatchEnvTracker;
use crate::util::env::load_env_variables_from_env_files;
use crate::util::v8::get_v8_flags_from_env;
use crate::util::v8::init_v8_flags;
#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
trait SubcommandOutput {
fn output(self) -> Result<i32, AnyError>;
}
impl SubcommandOutput for Result<i32, AnyError> {
fn output(self) -> Result<i32, AnyError> {
self
}
}
impl SubcommandOutput for Result<(), AnyError> {
fn output(self) -> Result<i32, AnyError> {
self.map(|_| 0)
}
}
impl SubcommandOutput for Result<(), std::io::Error> {
fn output(self) -> Result<i32, AnyError> {
self.map(|_| 0).map_err(|e| e.into())
}
}
#[inline(always)]
fn spawn_subcommand<F: Future<Output = T> + 'static, T: SubcommandOutput>(
f: F,
) -> JoinHandle<Result<i32, AnyError>> {
deno_core::unsync::spawn(
async move { f.map(|r| r.output()).await }.boxed_local(),
)
}
async fn run_subcommand(
flags: Flags,
unconfigured_runtime: Option<UnconfiguredRuntime>,
roots: LibWorkerFactoryRoots,
) -> Result<i32, AnyError> {
let handle = match flags.subcommand.clone() {
DenoSubcommand::Add(add_flags) => spawn_subcommand(async {
tools::pm::add(Arc::new(flags), add_flags, tools::pm::AddCommandName::Add)
.await
}),
DenoSubcommand::Audit(audit_flags) => spawn_subcommand(async {
tools::pm::audit(Arc::new(flags), audit_flags).await
}),
DenoSubcommand::ApproveScripts(approve_scripts_flags) => {
spawn_subcommand(async move {
tools::pm::approve_scripts(Arc::new(flags), approve_scripts_flags).await
})
}
DenoSubcommand::Remove(remove_flags) => spawn_subcommand(async {
tools::pm::remove(Arc::new(flags), remove_flags).await
}),
DenoSubcommand::Link(link_flags) => spawn_subcommand(async {
tools::pm::link(Arc::new(flags), link_flags).await
}),
DenoSubcommand::Unlink(unlink_flags) => spawn_subcommand(async {
tools::pm::unlink(Arc::new(flags), unlink_flags).await
}),
DenoSubcommand::Bench(bench_flags) => spawn_subcommand(async {
let flags = Arc::new(flags);
if flags.watch.is_some() {
tools::bench::run_benchmarks_with_watch(flags, bench_flags)
.boxed_local()
.await
} else {
tools::bench::run_benchmarks(flags, bench_flags).await
}
}),
DenoSubcommand::Bundle(bundle_flags) => spawn_subcommand(async {
log::warn!(
"⚠️ {} is experimental and subject to changes",
colors::cyan("deno bundle")
);
tools::bundle::bundle(Arc::new(flags), bundle_flags).await
}),
DenoSubcommand::Deploy(deploy_flags) => spawn_subcommand(async move {
tools::deploy::deploy(flags, deploy_flags).await
}),
DenoSubcommand::Doc(doc_flags) => spawn_subcommand(async {
tools::doc::doc(Arc::new(flags), doc_flags).await
}),
DenoSubcommand::Eval(eval_flags) => spawn_subcommand(async {
tools::run::eval_command(Arc::new(flags), eval_flags).await
}),
DenoSubcommand::Cache(cache_flags) => spawn_subcommand(async move {
tools::installer::install_from_entrypoints(
Arc::new(flags),
self::args::InstallEntrypointsFlags {
entrypoints: cache_flags.files,
lockfile_only: false,
production: false,
skip_types: false,
},
)
.await
}),
DenoSubcommand::Check(check_flags) => spawn_subcommand(async move {
tools::check::check(Arc::new(flags), check_flags).await
}),
DenoSubcommand::Clean(clean_flags) => spawn_subcommand(async move {
tools::clean::clean(Arc::new(flags), clean_flags).await
}),
DenoSubcommand::Compile(compile_flags) => spawn_subcommand(async {
tools::compile::compile(flags, compile_flags).await
}),
DenoSubcommand::Desktop(desktop_flags) => spawn_subcommand(async {
Box::pin(tools::desktop::desktop(flags, desktop_flags)).await
}),
DenoSubcommand::Coverage(coverage_flags) => spawn_subcommand(async move {
let reporter =
crate::tools::coverage::reporter::create(coverage_flags.r#type.clone());
tools::coverage::cover_files(
Arc::new(flags),
coverage_flags.files.include,
coverage_flags.files.ignore,
coverage_flags.include,
coverage_flags.exclude,
coverage_flags.output,
coverage_flags.threshold.map(|t| t as f64),
&[&*reporter],
)
}),
DenoSubcommand::Fmt(fmt_flags) => spawn_subcommand(async move {
tools::fmt::format(Arc::new(flags), fmt_flags).await
}),
DenoSubcommand::Transpile(transpile_flags) => {
spawn_subcommand(async move {
log::warn!(
"⚠️ {} is experimental and subject to changes",
colors::cyan("deno transpile")
);
tools::transpile::transpile(Arc::new(flags), transpile_flags).await
})
}
DenoSubcommand::Init(init_flags) => spawn_subcommand(async {
tools::init::init_project(flags, init_flags).await
}),
DenoSubcommand::Info(info_flags) => spawn_subcommand(async {
tools::info::info(Arc::new(flags), info_flags).await
}),
DenoSubcommand::List(list_flags) => spawn_subcommand(async {
tools::pm::list(Arc::new(flags), list_flags).await
}),
DenoSubcommand::Install(install_flags) => spawn_subcommand(async {
tools::installer::install_command(Arc::new(flags), install_flags).await
}),
DenoSubcommand::Ci(ci_flags) => spawn_subcommand(async {
tools::installer::ci_command(Arc::new(flags), ci_flags).await
}),
DenoSubcommand::JSONReference(json_reference) => {
spawn_subcommand(async move {
display::write_json_to_stdout(&json_reference.json)
})
}
DenoSubcommand::Jupyter(jupyter_flags) => spawn_subcommand(async {
tools::jupyter::kernel(Arc::new(flags), jupyter_flags).await
}),
DenoSubcommand::Uninstall(uninstall_flags) => spawn_subcommand(async {
tools::installer::uninstall(Arc::new(flags), uninstall_flags).await
}),
DenoSubcommand::Lsp => spawn_subcommand(async move {
if std::io::stderr().is_terminal() {
log::warn!(
"{} command is intended to be run by text editors and IDEs and shouldn't be run manually.
Visit https://docs.deno.com/runtime/getting_started/setup_your_environment/ for instruction
how to setup your favorite text editor.
Press Ctrl+C to exit.
", colors::cyan("deno lsp"));
}
lsp::start().await
}),
DenoSubcommand::Lint(lint_flags) => spawn_subcommand(async {
if lint_flags.rules {
tools::lint::print_rules_list(
lint_flags.json,
lint_flags.maybe_rules_tags,
);
Ok(())
} else {
tools::lint::lint(Arc::new(flags), lint_flags).await
}
}),
DenoSubcommand::Outdated(update_flags) => spawn_subcommand(async move {
tools::pm::outdated(Arc::new(flags), update_flags).await
}),
DenoSubcommand::Repl(repl_flags) => spawn_subcommand(async move {
tools::repl::run(Arc::new(flags), repl_flags).await
}),
DenoSubcommand::X(x_flags) => {
spawn_subcommand(
async move { tools::x::run(Arc::new(flags), x_flags).await },
)
}
DenoSubcommand::Run(run_flags) => spawn_subcommand(async move {
if run_flags.print_task_list {
let task_flags = TaskFlags {
cwd: None,
task: None,
is_run: true,
recursive: false,
filter: None,
eval: false,
no_prefix: false,
concurrency: None,
if_present: false,
};
let mut flags = flags;
flags.subcommand = DenoSubcommand::Task(task_flags.clone());
writeln!(
&mut std::io::stdout(),
"Please specify a {} or a {}.\n",
colors::bold("[SCRIPT_ARG]"),
colors::bold("task name")
)?;
std::io::stdout().flush()?;
tools::task::execute_script(Arc::new(flags), task_flags)
.await
.map(|_| 1)
} else if run_flags.is_stdin() {
tools::run::run_from_stdin(Arc::new(flags), unconfigured_runtime, roots)
.boxed_local()
.await
} else if flags.eszip {
tools::run::run_eszip(
Arc::new(flags),
run_flags,
unconfigured_runtime,
roots,
)
.boxed_local()
.await
} else {
let flags = Arc::new(flags);
let result = tools::run::run_script(
WorkerExecutionMode::Run,
flags.clone(),
flags.watch.clone(),
unconfigured_runtime,
roots.clone(),
)
.await;
match result {
Ok(v) => Ok(v),
Err(script_err) => {
if let Some(
worker::CreateCustomWorkerError::ResolvePkgFolderFromDenoReq(
ResolvePkgFolderFromDenoReqError::Byonm(
ByonmResolvePkgFolderFromDenoReqError::UnmatchedReq(_),
),
),
) = any_and_jserrorbox_downcast_ref::<
worker::CreateCustomWorkerError,
>(&script_err)
&& flags.node_modules_dir.is_none()
{
let mut flags = flags.deref().clone();
let watch = flags.watch.clone();
flags.node_modules_dir =
Some(deno_config::deno_json::NodeModulesDirMode::None);
if flags.frozen_lockfile.is_none() {
flags.internal.lockfile_skip_write = true;
}
return tools::run::run_script(
WorkerExecutionMode::Run,
Arc::new(flags),
watch,
None,
roots,
)
.boxed_local()
.await;
}
let script_err_msg = script_err.to_string();
if should_fallback_on_run_error(script_err_msg.as_str()) {
if run_flags.bare {
let mut cmd = args::clap_root();
cmd.build();
let command_names = cmd
.get_subcommands()
.map(|command| command.get_name())
.collect::<Vec<_>>();
let suggestions =
args::did_you_mean(&run_flags.script, command_names);
if !suggestions.is_empty() && !run_flags.script.contains('.') {
let mut error =
clap::error::Error::<clap::error::DefaultFormatter>::new(
clap::error::ErrorKind::InvalidSubcommand,
)
.with_cmd(&cmd);
error.insert(
clap::error::ContextKind::InvalidSubcommand,
clap::error::ContextValue::String(run_flags.script.clone()),
);
error.insert(
clap::error::ContextKind::SuggestedSubcommand,
clap::error::ContextValue::Strings(suggestions),
);
Err(error.into())
} else {
Err(script_err)
}
} else {
let mut new_flags = flags.deref().clone();
let task_flags = TaskFlags {
cwd: None,
task: Some(run_flags.script.clone()),
is_run: true,
recursive: false,
filter: None,
eval: false,
no_prefix: false,
concurrency: None,
if_present: false,
};
new_flags.subcommand = DenoSubcommand::Task(task_flags.clone());
let result = tools::task::execute_script(
Arc::new(new_flags),
task_flags.clone(),
)
.await;
match result {
Ok(v) => Ok(v),
Err(_) => {
Err(script_err)
}
}
}
} else {
Err(script_err)
}
}
}
}
}),
DenoSubcommand::Serve(serve_flags) => spawn_subcommand(async move {
tools::serve::serve(
Arc::new(flags),
serve_flags,
unconfigured_runtime,
roots,
)
.await
}),
DenoSubcommand::Task(task_flags) => spawn_subcommand(async {
tools::task::execute_script(Arc::new(flags), task_flags).await
}),
DenoSubcommand::Test(test_flags) => {
spawn_subcommand(async {
if let Some(ref coverage_dir) = test_flags.coverage_dir {
if !test_flags.coverage_raw_data_only || test_flags.clean {
let _ = std::fs::remove_dir_all(coverage_dir);
}
std::fs::create_dir_all(coverage_dir)
.with_context(|| format!("Failed creating: {coverage_dir}"))?;
unsafe {
env::set_var(
"DENO_COVERAGE_DIR",
canonicalize_path(&PathBuf::from(coverage_dir))?,
)
};
}
if flags.watch.is_some() {
tools::test::run_tests_with_watch(Arc::new(flags), test_flags).await
} else {
tools::test::run_tests(Arc::new(flags), test_flags).await
}
})
}
DenoSubcommand::Completions(completions_flags) => {
spawn_subcommand(async move {
match completions_flags {
CompletionsFlags::Static(buf) => {
deno_print::drop_write_stdout(&buf);
Ok::<_, AnyError>(())
}
CompletionsFlags::Dynamic(f) => {
f()?;
Ok(())
}
}
})
}
DenoSubcommand::Types => spawn_subcommand(async move {
let types = tsc::get_types_declaration_file_text();
deno_print::drop_write_stdout(types.as_bytes());
Ok::<_, AnyError>(())
}),
#[cfg(feature = "upgrade")]
DenoSubcommand::Upgrade(upgrade_flags) => spawn_subcommand(async {
tools::upgrade::upgrade(Arc::new(flags), upgrade_flags).await
}),
#[cfg(not(feature = "upgrade"))]
DenoSubcommand::Upgrade(_) => exit_with_message(
"This deno was built without the \"upgrade\" feature. Please upgrade using the installation method originally used to install Deno.",
1,
),
DenoSubcommand::Why(why_flags) => spawn_subcommand(async {
tools::pm::why(Arc::new(flags), why_flags).await
}),
DenoSubcommand::BumpVersion(version_flags) => spawn_subcommand(async {
log::warn!(
"{}",
colors::yellow(
"deno bump-version is experimental and subject to change"
)
);
tools::bump_version::bump_version_command(Arc::new(flags), version_flags)
}),
DenoSubcommand::Vendor => exit_with_message(
"⚠️ `deno vendor` was removed in Deno 2.\n\nSee the Deno 1.x to 2.x Migration Guide for migration instructions: https://docs.deno.com/runtime/manual/advanced/migrate_deprecations",
1,
),
DenoSubcommand::Publish(publish_flags) => spawn_subcommand(async {
tools::publish::publish(Arc::new(flags), publish_flags).await
}),
DenoSubcommand::Pack(pack_flags) => spawn_subcommand(async {
tools::pack::pack(flags.into(), pack_flags).await
}),
DenoSubcommand::Help(help_flags) => spawn_subcommand(async move {
use std::io::Write;
let mut stream = anstream::AutoStream::new(
std::io::stdout(),
if colors::use_color() {
anstream::ColorChoice::Auto
} else {
anstream::ColorChoice::Never
},
);
match stream.write_all(help_flags.help.as_bytes()) {
Ok(()) => Ok(()),
Err(e) => match e.kind() {
std::io::ErrorKind::BrokenPipe => Ok(()),
_ => Err(e),
},
}
}),
};
handle.await?
}
fn should_fallback_on_run_error(script_err: &str) -> bool {
if script_err.starts_with(MODULE_NOT_FOUND)
|| script_err.starts_with(UNSUPPORTED_SCHEME)
|| script_err.starts_with(UNSUPPORTED_DIR_IMPORT)
{
return true;
}
let re = lazy_regex::regex!(
r"Import 'file:///.+?' failed\.\n\s+0: .+ \(os error \d+\)"
);
re.is_match(script_err)
}
#[allow(clippy::print_stderr, reason = "panic hook")]
fn setup_panic_hook() {
let orig_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
if is_broken_pipe_print_panic(panic_info) {
deno_runtime::exit(1);
}
let panic_str = panic_info
.payload()
.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| panic_info.payload().downcast_ref::<&str>().copied())
.unwrap_or("");
let is_resource_limit = panic_str.contains("worker thread")
|| panic_str.contains("Resource temporarily unavailable")
|| panic_str.contains("could not start worker threads");
if is_resource_limit {
eprintln!(
"\n============================================================"
);
eprintln!("Deno could not start: unable to create worker threads.");
eprintln!();
eprintln!("This usually happens when running in a container with a low");
eprintln!("PID limit. Try increasing the limit (e.g. --pids-limit=40).");
eprintln!();
eprintln!("Platform: {} {}", env::consts::OS, env::consts::ARCH);
eprintln!("Version: {}", deno_lib::version::DENO_VERSION_INFO.deno);
eprintln!("Args: {:?}", env::args().collect::<Vec<_>>());
eprintln!();
deno_runtime::exit(1);
}
eprintln!("\n============================================================");
eprintln!("Deno has panicked. This is a bug in Deno. Please report this");
eprintln!("at https://github.com/denoland/deno/issues/new.");
eprintln!("If you can reliably reproduce this panic, include the");
eprintln!("reproduction steps and re-run with the RUST_BACKTRACE=1 env");
eprintln!("var set and include the backtrace in your report.");
eprintln!();
eprintln!("Platform: {} {}", env::consts::OS, env::consts::ARCH);
eprintln!("Version: {}", deno_lib::version::DENO_VERSION_INFO.deno);
eprintln!("Args: {:?}", env::args().collect::<Vec<_>>());
eprintln!();
#[cfg(feature = "panic-trace")]
{
let info = &deno_lib::version::DENO_VERSION_INFO;
let version =
if info.release_channel == deno_lib::shared::ReleaseChannel::Canary {
format!("{}+{}", deno_lib::version::DENO_VERSION, info.git_hash)
} else {
info.deno.to_string()
};
let trace = deno_panic::trace();
eprintln!("View stack trace at:");
eprintln!(
"https://panic.deno.com/v{}/{}/{}",
version,
env!("TARGET"),
trace
);
}
orig_hook(panic_info);
deno_runtime::exit(1);
}));
fn error_handler(file: &str, line: i32, message: &str) {
if message.contains("Check failed: Start()") {
panic!(
"Failed to initialize V8 platform (could not start worker threads). \
If running in a container, ensure the PID limit is high enough \
(try --pids-limit=40 or higher)."
);
}
panic!("Fatal error in {file}:{line}: {message}");
}
deno_core::v8::V8::set_fatal_error_handler(error_handler);
}
fn is_broken_pipe_print_panic(panic_info: &std::panic::PanicHookInfo) -> bool {
let payload = panic_info.payload();
let msg: &str = if let Some(s) = payload.downcast_ref::<String>() {
s.as_str()
} else if let Some(&s) = payload.downcast_ref::<&'static str>() {
s
} else {
return false;
};
if !msg.starts_with("failed printing to stdout:")
&& !msg.starts_with("failed printing to stderr:")
{
return false;
}
msg.contains("(os error 32)")
|| msg.contains("(os error 109)")
|| msg.contains("(os error 232)")
}
fn exit_with_message(message: &str, code: i32) -> ! {
log::error!(
"{}: {}",
colors::red_bold("error"),
message.trim_start_matches("error: ")
);
deno_runtime::exit(code);
}
fn exit_for_error(error: AnyError, initial_cwd: Option<&std::path::Path>) -> ! {
let mut error_string = match js_error_downcast_ref(&error) {
Some(e) => {
let initial_cwd = initial_cwd
.and_then(|cwd| deno_path_util::url_from_directory_path(cwd).ok());
format_js_error(e, initial_cwd.as_ref())
}
None => format!("{error:?}"),
};
if let Some(hint) = util::pnpm_workspace::maybe_auto_migrate_pnpm_workspace(
&error_string,
initial_cwd,
) {
error_string.push_str(&hint);
}
if let Some(hint) = maybe_minimum_dependency_age_hint(&error_string) {
error_string.push_str(&hint);
}
exit_with_message(&error_string, 1);
}
const MINIMUM_DEPENDENCY_AGE_MARKER: &str = "minimum dependency date";
fn maybe_minimum_dependency_age_hint(error_string: &str) -> Option<String> {
if !error_string.contains(MINIMUM_DEPENDENCY_AGE_MARKER) {
return None;
}
Some(format!(
concat!(
"\n\n{} This version is blocked by the minimum dependency age policy, ",
"which avoids installing recently published versions to reduce supply ",
"chain risk (the default is 24 hours). To use this version now, pass the ",
"{} (or {}) flag (for example {} to disable it, or a shorter duration ",
"like {} minutes) or set {} in your deno.json, or wait until the version ",
"is old enough.\n{} {}"
),
colors::yellow("hint:"),
colors::bold("--minimum-dependency-age"),
colors::bold("--min-dep-age"),
colors::bold("0"),
colors::bold("60"),
colors::bold("\"minimumDependencyAge\""),
colors::yellow("docs:"),
colors::cyan("https://docs.deno.com/go/minimum-dependency-age"),
))
}
pub(crate) fn unstable_exit_cb(feature: &str, api_name: &str) {
log::error!(
"Unstable API '{api_name}'. The `--unstable-{}` flag must be provided.",
feature
);
deno_runtime::exit(70);
}
fn maybe_setup_permission_broker() {
let Ok(socket_path) = std::env::var("DENO_PERMISSION_BROKER_PATH") else {
return;
};
log::warn!(
"{} Permission broker is an experimental feature",
colors::yellow("Warning")
);
let broker =
deno_runtime::deno_permissions::broker::PermissionBroker::new(socket_path);
deno_runtime::deno_permissions::broker::set_broker(broker);
}
#[inline(always)]
pub(crate) fn boot_phase(label: &str) {
use std::sync::OnceLock;
use std::time::Instant;
static START: OnceLock<Instant> = OnceLock::new();
static ENABLED: OnceLock<bool> = OnceLock::new();
let start = START.get_or_init(Instant::now);
#[allow(
clippy::disallowed_methods,
reason = "diagnostic env var; startup profiling only"
)]
let enabled =
*ENABLED.get_or_init(|| std::env::var_os("DENO_STARTUP_PHASES").is_some());
if enabled {
#[allow(clippy::print_stderr, reason = "diagnostic")]
{
eprintln!("[boot] {label:>28} {:?}", start.elapsed());
}
}
}
pub fn main() {
boot_phase("main start");
#[cfg(feature = "dhat-heap")]
let profiler = dhat::Profiler::new_heap();
setup_panic_hook();
init_logging(None, None);
boot_phase("after panic+logging");
util::unix::raise_fd_limit();
util::windows::ensure_stdio_open();
#[cfg(windows)]
{
deno_subprocess_windows::disable_stdio_inheritance();
colors::enable_ansi(); }
deno_runtime::deno_permissions::prompter::set_prompt_callbacks(
Box::new(util::draw_thread::DrawThread::hide),
Box::new(util::draw_thread::DrawThread::show),
);
maybe_setup_permission_broker();
boot_phase("before aws_lc install");
rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.unwrap();
boot_phase("after aws_lc install");
let args: Vec<_> = env::args_os().collect();
let args = node_compat_shim::maybe_rewrite_node_arg0(args);
let future = async move {
let roots = LibWorkerFactoryRoots::default();
#[cfg(unix)]
let (waited_unconfigured_runtime, waited_args, waited_cwd) =
match wait_for_start(&args, roots.clone()) {
Some(f) => match f.await {
Ok(v) => match v {
Some((u, a, c)) => (Some(u), Some(a), Some(c)),
None => (None, None, None),
},
Err(e) => {
panic!("Failure from control sock: {e}");
}
},
None => (None, None, None),
};
#[cfg(not(unix))]
let (waited_unconfigured_runtime, waited_args, waited_cwd) =
(None, None, None);
let args = waited_args.unwrap_or(args);
#[allow(
clippy::disallowed_methods,
reason = "ok because initialization of cwd"
)]
let initial_cwd =
waited_cwd
.map(Some)
.unwrap_or_else(|| match std::env::current_dir() {
Ok(cwd) => Some(cwd),
Err(err) => {
log::error!("Failed getting cwd: {err}");
None
}
});
let flags = match resolve_flags_and_init(args, initial_cwd.clone()).await {
Ok(flags) => flags,
Err(err) => return (Err(err), initial_cwd),
};
if waited_unconfigured_runtime.is_none() {
init_v8(&flags);
}
boot_phase("after init_v8");
(
run_subcommand(flags, waited_unconfigured_runtime, roots).await,
initial_cwd,
)
};
let (result, initial_cwd) =
create_and_run_current_thread_with_maybe_metrics(future);
#[cfg(feature = "dhat-heap")]
drop(profiler);
match result {
Ok(exit_code) => deno_runtime::exit(exit_code),
Err(err) => exit_for_error(err, initial_cwd.as_deref()),
}
}
async fn resolve_flags_and_init(
args: Vec<std::ffi::OsString>,
initial_cwd: Option<std::path::PathBuf>,
) -> Result<Flags, AnyError> {
if std::env::var("COMPLETE").is_ok() {
let cwd = resolve_cwd(initial_cwd.as_deref())?;
crate::args::handle_shell_completion(&cwd)?;
deno_runtime::exit(0);
}
boot_phase("before clap parse");
let mut flags =
match flags_from_vec_with_initial_cwd(args, initial_cwd.clone()) {
Ok(flags) => {
boot_phase("after clap parse");
flags
}
Err(err @ clap::Error { .. })
if err.kind() == clap::error::ErrorKind::DisplayVersion =>
{
boot_phase("clap parse (version exit)");
let _ = err.print();
deno_runtime::exit(0);
}
Err(err) => exit_for_error(AnyError::from(err), initial_cwd.as_deref()),
};
if flags.watch.is_some() {
WatchEnvTracker::snapshot();
}
if let Some(files) = &flags.env_file {
let cwd = match &flags.initial_cwd {
Some(cwd) => Cow::Borrowed(cwd),
None => match resolve_cwd(None) {
Ok(cwd) => {
flags.initial_cwd = Some(cwd.into_owned());
Cow::Borrowed(flags.initial_cwd.as_ref().unwrap())
}
Err(_) => Cow::Owned(PathBuf::from(".")),
},
};
load_env_variables_from_env_files(&cwd, files, flags.log_level);
}
let sys = crate::sys::CliSys::default();
if node_compat_shim::subcommand_may_spawn_node(&flags.subcommand) {
match deno_cache_dir::resolve_deno_dir(
&sys,
deno_cache_dir::ResolveDenoDirOptions {
maybe_initial_cwd: initial_cwd.as_deref(),
maybe_custom_root: None,
},
) {
Ok(deno_dir_root) => {
if let Err(err) = node_compat_shim::ensure_node_on_path(&deno_dir_root)
{
log::debug!("node compat shim setup failed (best-effort): {err}");
}
}
Err(err) => {
log::debug!("could not resolve DENO_DIR for node compat shim: {err}");
}
}
}
if deno_lib::args::has_flag_env_var(&sys, "DENO_CONNECTED") {
flags.tunnel = true;
}
if flags.tunnel && !matches!(flags.subcommand, DenoSubcommand::Deploy(_)) {
if let Err(err) = initialize_tunnel(&flags).await {
exit_for_error(
err.context("Failed to start with tunnel"),
initial_cwd.as_deref(),
);
}
unsafe {
std::env::set_var("DENO_CONNECTED", "1");
}
}
flags.unstable_config.fill_with_env(&sys);
if std::env::var("DENO_COMPAT").is_ok() {
flags.unstable_config.enable_node_compat();
}
if flags.node_conditions.is_empty()
&& let Ok(conditions) = std::env::var("DENO_CONDITIONS")
{
flags.node_conditions = conditions
.split(",")
.map(|c| c.trim().to_string())
.collect();
}
let otel_config = flags.otel_config();
init_logging(flags.log_level, Some(otel_config.clone()));
deno_telemetry::init(
&sys,
deno_lib::version::otel_runtime_config(),
otel_config.clone(),
)?;
if flags.permission_set.is_some() {
log::warn!(
"{} Permissions in the config file is an experimental feature and may change in the future.",
colors::yellow("Warning")
);
}
if flags.unstable_config.legacy_flag_enabled {
log::warn!(
"{} The `--unstable` flag has been removed in Deno 2.0. Use granular `--unstable-*` flags instead.\nLearn more at: https://docs.deno.com/runtime/manual/tools/unstable_flags",
colors::yellow("Warning")
);
}
if let Ok(audit_target) = std::env::var("DENO_AUDIT_PERMISSIONS") {
use deno_runtime::deno_permissions::AuditSink;
let sink = if audit_target == "otel" {
AuditSink::Otel(|permission, value, stack| {
let stack = stack.unwrap_or_default().join("\n");
let kvs = HashMap::from([
("deno.permission.type", permission),
("deno.permission.value", value),
("deno.permission.stack", stack.as_str()),
]);
deno_telemetry::handle_log(
&log::Record::builder()
.level(log::Level::Info)
.target("deno.permission.access")
.args(format_args!("{permission}: {value}"))
.key_values(&kvs)
.build(),
);
})
} else {
AuditSink::File(deno_core::parking_lot::Mutex::new(
std::fs::File::create(audit_target)?,
))
};
let result = deno_runtime::deno_permissions::AUDIT_SINK.set(sink);
if result.is_err() {
log::warn!("⚠️ {}", colors::yellow("Audit sink is already set"));
}
}
Ok(flags)
}
fn init_v8(flags: &Flags) {
let default_v8_flags = match flags.subcommand {
DenoSubcommand::Lsp => vec![
"--stack-size=1024".to_string(),
"--max-old-space-size=3072".to_string(),
],
_ => get_default_v8_flags(),
};
let env_v8_flags = get_v8_flags_from_env();
let is_single_threaded = env_v8_flags
.iter()
.chain(&flags.v8_flags)
.any(|flag| flag == "--single-threaded");
init_v8_flags(&default_v8_flags, &flags.v8_flags, env_v8_flags);
let v8_platform = if is_single_threaded {
Some(::deno_core::v8::Platform::new_single_threaded(true).make_shared())
} else {
None
};
deno_core::JsRuntime::init_platform(v8_platform);
}
fn init_logging(
maybe_level: Option<log::Level>,
otel_config: Option<OtelConfig>,
) {
deno_lib::util::logger::init(deno_lib::util::logger::InitLoggingOptions {
maybe_level,
otel_config,
on_log_start: DrawThread::hide,
on_log_end: DrawThread::show,
})
}
#[cfg(unix)]
#[allow(clippy::type_complexity, reason = "private code")]
fn wait_for_start(
args: &[std::ffi::OsString],
roots: LibWorkerFactoryRoots,
) -> Option<
impl Future<
Output = Result<
Option<(UnconfiguredRuntime, Vec<std::ffi::OsString>, PathBuf)>,
AnyError,
>,
> + use<>,
> {
let startup_snapshot = deno_snapshots::CLI_SNAPSHOT?;
let addr = std::env::var("DENO_UNSTABLE_CONTROL_SOCK").ok()?;
unsafe { std::env::remove_var("DENO_UNSTABLE_CONTROL_SOCK") };
let argv0 = args[0].clone();
Some(async move {
use tokio::io::AsyncBufReadExt;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
use tokio::io::AsyncWriteExt;
use tokio::io::BufReader;
use tokio::net::UnixSocket;
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "macos"
))]
use tokio_vsock::VsockAddr;
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "macos"
))]
use tokio_vsock::VsockListener;
init_v8(&Flags::default());
let unconfigured = deno_runtime::UnconfiguredRuntime::new::<
deno_resolver::npm::DenoInNpmPackageChecker,
crate::npm::CliNpmResolver,
crate::sys::CliSys,
>(deno_runtime::UnconfiguredRuntimeOptions {
startup_snapshot,
residual_lazy_js_sources: deno_snapshots::RESIDUAL_LAZY_JS,
residual_lazy_esm_sources: deno_snapshots::RESIDUAL_LAZY_ESM,
create_params: deno_lib::worker::create_isolate_create_params(
&crate::sys::CliSys::default(),
),
shared_array_buffer_store: Some(roots.shared_array_buffer_store.clone()),
compiled_wasm_module_store: Some(
roots.compiled_wasm_module_store.clone(),
),
additional_extensions: vec![],
});
let (rx, mut tx): (
Box<dyn AsyncRead + Unpin>,
Box<dyn AsyncWrite + Send + Unpin>,
) = match addr.split_once(':') {
Some(("unix", path)) => {
let socket = UnixSocket::new_stream()?;
socket.bind(path)?;
let listener = socket.listen(1)?;
let (stream, _) = listener.accept().await?;
let (rx, tx) = stream.into_split();
(Box::new(rx), Box::new(tx))
}
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "macos"
))]
Some(("vsock", addr)) => {
let Some((cid, port)) = addr.split_once(':') else {
deno_core::anyhow::bail!("invalid vsock addr");
};
let cid = if cid == "-1" { u32::MAX } else { cid.parse()? };
let port = port.parse()?;
let addr = VsockAddr::new(cid, port);
let listener = VsockListener::bind(addr)?;
let (stream, _) = listener.accept().await?;
let (rx, tx) = stream.into_split();
(Box::new(rx), Box::new(tx))
}
_ => {
deno_core::anyhow::bail!("invalid control sock");
}
};
let mut buf = Vec::with_capacity(1024);
BufReader::new(rx).read_until(b'\n', &mut buf).await?;
tokio::spawn(async move {
deno_runtime::deno_http::SERVE_NOTIFIER.notified().await;
#[derive(deno_core::serde::Serialize)]
enum Event {
Serving {
#[serde(skip_serializing_if = "Option::is_none")]
kind: Option<&'static str>,
},
}
let mut buf = deno_core::serde_json::to_vec(&Event::Serving {
kind: deno_runtime::deno_http::serving_server_kind(),
})
.unwrap();
buf.push(b'\n');
let _ = tx.write_all(&buf).await;
});
#[derive(deno_core::serde::Deserialize)]
struct Start {
cwd: String,
args: Vec<String>,
env: Vec<(String, String)>,
}
let cmd: Start = deno_core::serde_json::from_slice(&buf)?;
std::env::set_current_dir(&cmd.cwd)?;
for (k, v) in cmd.env {
unsafe { std::env::set_var(k, v) };
}
let args = [argv0]
.into_iter()
.chain(cmd.args.into_iter().map(Into::into))
.collect();
Ok(Some((unconfigured, args, PathBuf::from(cmd.cwd))))
})
}
#[derive(serde::Deserialize)]
struct AuthTunnelOutput {
org: String,
app: String,
token: String,
}
async fn auth_tunnel(
no_config: bool,
env_token: Option<String>,
) -> Result<AuthTunnelOutput, deno_core::anyhow::Error> {
let file = tempfile::NamedTempFile::new()?;
let mut args = vec![];
if let Some(token) = &env_token {
args.push("--token".to_string());
args.push(token.clone());
}
if no_config {
args.push("--really-no-config".into());
}
let mut child = tokio::process::Command::new(env::current_exe()?)
.arg("deploy")
.arg("tunnel-login")
.arg("--out")
.arg(file.path())
.args(args)
.spawn()?;
let out = child.wait().await?;
if !out.success() {
deno_runtime::exit(1);
}
let contents = tokio::fs::read(file.path()).await?;
let output: AuthTunnelOutput = deno_core::serde_json::from_slice(&contents)?;
Ok(output)
}
async fn initialize_tunnel(
flags: &Flags,
) -> Result<(), deno_core::anyhow::Error> {
let factory = CliFactory::from_flags(Arc::new(flags.clone()));
let cli_options = factory.cli_options()?;
let start_dir = &cli_options.start_dir;
let deploy_config = cli_options
.start_dir
.to_deploy_config(FilePatterns::new_with_base(start_dir.dir_path()))?;
let no_config = flags.config_flag == crate::args::ConfigFlag::Disabled;
let host = std::env::var("DENO_DEPLOY_TUNNEL_ENDPOINT")
.unwrap_or_else(|_| "tunnel.global.prod.deno-cluster.net:443".into());
let env_token = env::var("DENO_DEPLOY_TOKEN").ok();
let env_org = env::var("DENO_DEPLOY_ORG").ok();
let env_app = env::var("DENO_DEPLOY_APP").ok();
let storage_token = tools::deploy::get_token_entry()
.and_then(|e| e.get_password())
.ok();
let (token, org, app) = if let (Some(token), Some(org), Some(app)) =
(&env_token, env_org, env_app)
{
(token.clone(), org, app)
} else if let Some(deploy_config) = &deploy_config
&& let Some(app) = &deploy_config.app
&& let Some(token) = &env_token
{
(token.clone(), deploy_config.org.clone(), app.clone())
} else if let Some(deploy_config) = deploy_config
&& let Some(token) = storage_token
&& let Some(app) = deploy_config.app
{
(token, deploy_config.org, app)
} else {
let o = auth_tunnel(no_config, env_token.clone()).await?;
(o.token, o.org, o.app)
};
let Some(addr) = tokio::net::lookup_host(&host).await?.next() else {
return Ok(());
};
let Some((hostname, _)) = host.split_once(':') else {
return Ok(());
};
let cert_store_provider = factory.root_cert_store_provider();
let root_cert_store = cert_store_provider.get_or_try_init()?.clone();
let tls_config = deno_runtime::deno_tls::create_client_config(
deno_runtime::deno_tls::TlsClientConfigOptions {
root_cert_store: Some(root_cert_store),
ca_certs: vec![],
unsafely_ignore_certificate_errors: None,
unsafely_disable_hostname_verification: false,
cert_chain_and_key: deno_runtime::deno_tls::TlsKeys::Null,
socket_use: deno_runtime::deno_tls::SocketUse::GeneralSsl,
},
)?;
let mut metadata = HashMap::new();
metadata.insert(
"hostname".into(),
deno_runtime::deno_os::sys_info::hostname(),
);
match &flags.subcommand {
DenoSubcommand::Run(run_flags) => {
metadata.insert("subcommand".into(), "run".into());
metadata.insert("entrypoint".into(), run_flags.script.clone());
}
DenoSubcommand::Serve(serve_flags) => {
metadata.insert("subcommand".into(), "serve".into());
metadata.insert("entrypoint".into(), serve_flags.script.clone());
}
DenoSubcommand::Task(task_flags) => {
metadata.insert("subcommand".into(), "task".into());
if let Some(task) = &task_flags.task {
metadata.insert("task".into(), task.clone());
}
}
DenoSubcommand::Repl(_) => {
metadata.insert("subcommand".into(), "repl".into());
}
DenoSubcommand::Eval(_) => {
metadata.insert("subcommand".into(), "eval".into());
}
_ => {}
}
let on_event = |event| {
use deno_runtime::deno_net::tunnel::Event;
match event {
Event::Routed(addr) => {
let endpoint = if addr.port() == 443 {
format!("https://{}", addr.hostname())
} else {
format!("https://{}:{}", addr.hostname(), addr.port())
};
#[allow(clippy::print_stderr, reason = "can't use log crate yet")]
{
eprintln!(
"{}",
colors::green(format!("You are connected to {endpoint}"))
);
}
}
Event::Reconnect(duration, reason) => {
let reason = if let Some(reason) = reason {
format!(" ({reason})")
} else {
"".into()
};
#[allow(clippy::print_stderr, reason = "can't use log crate yet")]
{
eprintln!(
"{}",
colors::green(format!(
"Reconnecting tunnel in {}s...{}",
duration.as_secs(),
reason
))
);
}
}
_ => {}
}
};
let tunnel = match deno_runtime::deno_net::tunnel::TunnelConnection::connect(
addr,
hostname.to_owned(),
tls_config.clone(),
deno_runtime::deno_net::tunnel::Authentication::App {
token,
org: org.clone(),
app: app.clone(),
},
metadata.clone(),
on_event,
)
.await
{
Ok(res) => res,
Err(deno_runtime::deno_net::tunnel::Error::Unauthorized) => {
if let Ok(e) = tools::deploy::get_token_entry() {
let _ = e.delete_credential();
}
let output = auth_tunnel(no_config, env_token).await?;
deno_runtime::deno_net::tunnel::TunnelConnection::connect(
addr,
hostname.to_owned(),
tls_config,
deno_runtime::deno_net::tunnel::Authentication::App {
token: output.token,
org: output.org,
app: output.app,
},
metadata.clone(),
on_event,
)
.await?
}
Err(e) => return Err(e.into()),
};
if let Some(metadata) = tunnel.metadata() {
for (k, v) in metadata.env {
unsafe {
std::env::set_var(k, v);
}
}
}
deno_runtime::deno_net::tunnel::set_tunnel(tunnel);
Ok(())
}